Mandelbrot Explorer Demo Source

by Bernie Freidin © 1999-2000



main.cpp


// -------------------------------
// MANDEL: Mandelbrot Set Explorer
// (c) Bernie Freidin, 1999
// -------------------------------

/*
Keys:
	C: clear screen
	D: redraw
	J: Julia mode
	M: Mandelbrot mode
	H: hybrid mode
	B: apply iteration bias
	BACKSPACE: revert to previous zoom state
	ESCAPE: quit
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <gl/glut.h>

#define VERSION (0xDEADC0DE + 0x000)

// Performance Measurements
// 1:18:42 - blend
// 1:14:34 - no blend
// 1:04:19 - blend/tight
// 0:59:82 - no blend/tight
// 0:22:64 - blend/release
// 0:20:16 - blend/tight/release
// 19.12 secs - blend/release/no redraw
// 0:20:41 - blend/release/esc@1e-15
// 0:19:63 - blend/release/esc@1e-12
// ~40 sec - blend/release/esc@1e-15 (PII-350)

#define _min(a,b) ((a)<(b)?(a):(b))
#define _max(a,b) ((a)>(b)?(a):(b))

#define ICON_BAR       0
#define STAT_BAR       100
#define MIN_WINDOW_X   128
#define MIN_WINDOW_Y   96
#define MAX_WINDOW_X   1280
#define MAX_WINDOW_Y   1024
#define MAX_SLICE_RES  16
#define MAX_ITERATIONS 32768
#define MAX_ZOOMS      250
#define MAX_C_ENTRIES  16 // color entries per preset
#define MAX_C_PRESETS  21 // max total preset count
#define C_PRESET_PATH "ColorPresets.txt"

typedef long pix_t; // 32bpp

#define MAX_JULIA_DEFAULT_PRESETS 10 // including SCvC

static double JULIA_DEFAULT_COORDS[MAX_JULIA_DEFAULT_PRESETS*2] = {
	-0.750, 0.00,    // parabolic fixed, period 2
	-0.125, 0.64925, // parabolic fixed, period 3
	-1.000, 0.00,    // superattractive, period 2
	-0.130, 0.76,    // superattractive, period 3
	+0.280, 0.53,    // superattractive, period 4
	-0.830, 0.16,    // swirly, period 2
	-.3905407802, -.5867879073, // Siegel Disc
	-0.1011, 0.9563, // Misiurewicz Point
	+0.000, 1.00,    // dendrite
	-9.0, -9.0
};

static char JULIA_DEFAULT_NAMES[MAX_JULIA_DEFAULT_PRESETS][64] = {
	"parabolic fixed point (period 2)",
	"parabolic fixed point (period 3)",
	"superattractive (period 2)",
	"superattractive (period 3)",
	"superattractive (period 4)",
	"swirly (period 2)",
	"Siegel Disc",
	"Misiurewicz Point",
	"dendrite",
	"Set Coordinates via Console..."
};

struct zoom_t
{
	double x, y, z;
	int iteration_bias;
};

struct box_t
{
	int x1, y1, x2, y2;
};

struct cdat_t
{
	char   name[48];
	int    count;
	double exponent;
	double colors[4*MAX_C_ENTRIES];
	pix_t  table[MAX_ITERATIONS+1];
};

static double STAT_BAR_COLOR[]   = {0.2, 0.2, 0.4};
static double STAT_TEXT_COLOR[]  = {0.5, 1.0, 0.5};
static double STAT_TEXT_COLOR2[] = {0.0, 1.0, 1.0};
static double BOX_NO_HLT_COLOR[] = {0.5, 0.5, 0.0};
static double BOX_HILITE_COLOR[] = {1.0, 1.0, 0.0};
static double SLICE_HLT_COLOR[]  = {1.0, 1.0, 1.0};

static int    WINDOW_X;   // total width  of window, in pixels
static int    WINDOW_Y;   // total height of window, in pixels
static int    VIEW_X;     // width  of viewing region, in pixels
static int    VIEW_Y;     // height of viewing region, in pixels
static box_t  BOX[2];     // zoom box (0=current, 1=old)
static int    BOX_STATE;  // internal tracking state
static int    BOX_HILITE; // internal hilite state
static int    BOX_UPDATE; // zoom box needs update?
static pix_t *SLICE_BUF;       // working buffer for image
static int    SLICE_POS;       // current vertical slice position
static int    SLICE_RES;       // current block size
static int    SLICE_X;         // VIEW_X/SLICE_RES
static int    SLICE_Y;         // VIEW_Y/SLICE_RES
static int    SLICE_UPDATE;    // slice needs update?
static int    SLICE_TMP_COUNT; // internal counter
static int    SLICE_HLT_POS;   // hilite position
static int    BLOCK_SIZE;      // block resolution (16,8,4,2,or 1)
static int    SSAMP_SIZE;      // supersampling grid size
static double FOCUS;           // supersampling focus
static int    ITERATION_COUNT[4];    // iteration counters
static int    ITERATIONS_PER_PIXEL;  // max iterations per pixel
static int    ITERATIONS_PER_UPDATE; // max iterations per update
static int    RENDERING_ENABLED;     // i.e. !"paused"
static int    REALTIME_STAT_UPDATE;  // update status after slice?
static int   *LOG2TABLE;             // little lookup thingy
static zoom_t ZOOM_LIST[MAX_ZOOMS];  // zoom states
static int    ZOOM_COUNT;            // number of zooms
static int    APP_RESHAPE_EVENT;     // internal flag
static double MOUSE_X;               // mouse x-coord in worldspace
static double MOUSE_Y;               // mouse y-coord in worldspace
static int    MOUSE_UPDATE;          // mouse moved - update coords
static int    MOUSE_ITERATIONS;      // iterations under mouse point
static int    JULIA_MODE;            // render Julia set?
static double JULIA_X;               // Julia x coefficient
static double JULIA_Y;               // Julia y coefficient
static int    JULIA_ZOOM_INDEX;      // index into zoom list where
                                     // Julia-mode was invoked..
static int    M_ID[20];
static char   C_PRESET_NAMES[MAX_C_PRESETS][48];
static int    C_PRESET_COUNT;
static int    HYSTERIA_MODE;    // (c) James Beattie, 1999
static double HYSTERIA_FACTOR;  // (c) James Beattie, 1999

static int    ESCAPE1_COLOR_DEF; // method used to color pixels
static int    ESCAPE2_COLOR_DEF; // method used to color pixels
static cdat_t ESCAPE1_COLOR_DAT; // color gradient lookups
static cdat_t ESCAPE2_COLOR_DAT; // color gradient lookups
static double ESCAPE2_THRESHOLD; // threshold for alternate escape*
static int    ESCAPE2_EQUATION;  // equation for alternate escape*
static int    ESCAPE2_MIN_ITER;  // minimum iterations for alt-esc
static int    ESCAPE2_MAX_ITER;  // maximum iterations for alt-esc
static double ESCAPE1_BLEND_FACTOR; // advanced stuff
static double ESCAPE1_XBAND_FACTOR; // advanced stuff
static double ESCAPE2_SHADE_FACTOR; // advanced stuff

// NOTE: Alternate escapes are activated when some function of
// x, y, dx, and/or dy in the Iteration() function comes within
// some threshold of zero. When this happens, the function exits,
// coloring the pixel based on the alternate color gradient.
// Visually, this looks like zillions of objects (spheres, wires,
// rings, blobs, etc.) are clustering around the boundary and
// interior of the Mandelbrot set.

static void SaveSliceBuffer(FILE *fp)
{
	// *****************************************************
	// Saves the slice buffer to a file. Note that the
	// pixels are stored in a multiresolution format so that
	// images that have not fully rendered do not take up a
	// huge amount of memory.
	// *****************************************************
	
	int y, ymark = (SLICE_HLT_POS + SLICE_RES) & ~(SLICE_RES*2-1);
	
	for(y = 0; y < ymark; y += SLICE_RES)
	{
		pix_t *buf = SLICE_BUF + (SLICE_RES+(y+1)*2)*(VIEW_X/2) - SLICE_X;
		
		fwrite(buf, sizeof(pix_t), SLICE_X, fp);
	}
	if(SLICE_RES == MAX_SLICE_RES) return;
	
	for(; y < VIEW_Y; y += SLICE_RES*2)
	{
		pix_t *buf = SLICE_BUF + (SLICE_RES+y+1)*VIEW_X - SLICE_X/2;
		
		fwrite(buf, sizeof(pix_t), SLICE_X/2, fp);
	}
}

static void LoadSliceBuffer(FILE *fp)
{
	// *****************************************************
	// Loads in the slice buffer from a file. Note that the
	// pixels are stored in a multiresolution format so that
	// images that have not fully rendered do not take up a
	// huge amount of memory.
	// *****************************************************
	
	int y, ymark = (SLICE_HLT_POS + SLICE_RES) & ~(SLICE_RES*2-1);
	
	memset(SLICE_BUF, 0, sizeof(pix_t)*VIEW_X*VIEW_Y);
	
	for(y = 0; y < ymark; y += SLICE_RES)
	{
		pix_t *buf = SLICE_BUF + (SLICE_RES+(y+1)*2)*(VIEW_X/2) - SLICE_X;
		
		fread(buf, sizeof(pix_t), SLICE_X, fp);
	}
	if(SLICE_RES == MAX_SLICE_RES) return;
	
	for(; y < VIEW_Y; y += SLICE_RES*2)
	{
		pix_t *buf = SLICE_BUF + (SLICE_RES+y+1)*VIEW_X - SLICE_X/2;
		
		fread(buf, sizeof(pix_t), SLICE_X/2, fp);
	}
}

// *******************
// Function prototypes
// *******************

static void  Initialize(void);
static void  Clear(void);
static void  MenuCallback_0(int id);
static void  MenuCallback_1(int id);
static void  MenuCallback_2(int id);
static void  MenuCallback_3(int id);
static void  MenuCallback_4(int id);
static void  MenuCallback_5(int id);
static void  MenuCallback_6(int id);
static void  MenuCallback_7(int id);
static void  MenuCallback_8(int id);
static void  MenuCallback_9(int id);
static void  MenuCallback_10(int id);
static void  MenuCallback_11(int id);
static void  MenuCallback_12(int id);
static void  MenuCallback_13(int id);
static void  MenuCallback_14(int id); // Julia presets
static void  UpdateMenu_1(void);
static void  UpdateMenu_2(void);
static void  UpdateMenu_3(void);
static void  UpdateMenu_4(void);
static void  UpdateMenu_5(void);
static void  UpdateMenu_6(void);
static void  UpdateMenu_7(void);
static void  UpdateMenu_8(void);
static void  UpdateMenu_9(void);
static void  UpdateMenu_10(void);
static void  UpdateMenu_11(void);
static void  UpdateMenu_13(void);
static int   QFileExists(char *path);
static int   QDisableRendering(void);
static int   QPointInBox(box_t *box, int x, int y);
static void  SaveImageAsTGA(char *path);
static void  SaveImageAsBMP(char *path);
static void  LoadColorPresetList(char *path);
static void  LoadColorPreset(cdat_t *cdat, char *path, char *name);
static void  RecomputeColorGradient(cdat_t *cdat);
static void  SetPixelCoords(int lft, int bot, int rgt, int top, int clear);
static void  Pix2WorldCoords(double &x, double &y);
static void  DrawText(int x, int y, char *str);
static void  IncrementCount(int dcount);
static void  AppendCountStr(char *str);
static void  DrawStatus(int field);
static pix_t Blend(pix_t *table, int index, double blend);
static pix_t Iterate(double cx, double cy, int &iter);
static pix_t RenderPixel(int i, int j);
static void  RedrawXLine(int x, int y, int width);
static void  RedrawYLine(int x, int y, int height);
static void  Redraw(void);
static void  EraseBox(box_t *box);
static void  DrawBox(box_t *box);
static int   DrawSlice(void);
static void  DrawSliceHilite(void);
static void  Zoom(box_t *box);

static void Initialize(void)
{
	int i, j;
	
	SLICE_BUF = (pix_t*)malloc(sizeof(pix_t)*MAX_WINDOW_X*MAX_WINDOW_Y);
	LOG2TABLE = (int*)calloc(sizeof(int), MAX_SLICE_RES+1);
	
	for(i = 1, j = 0; i <= MAX_SLICE_RES; i *= 2, j++)
	{
		LOG2TABLE[i] = j;
	}
	BLOCK_SIZE = 1;
	SSAMP_SIZE = 4;
	FOCUS      = 1.1;
	
	ITERATIONS_PER_PIXEL  = 256; // must NOT exceed MAX_ITERATIONS
	ITERATIONS_PER_UPDATE = 20000;
	RENDERING_ENABLED     = 1;
	REALTIME_STAT_UPDATE  = 1;
	ZOOM_LIST[0].x        = 0.5; // default zoom
	ZOOM_LIST[0].y        = 0.0;
	ZOOM_LIST[0].z        = 0.75;
	ZOOM_LIST[0].iteration_bias = 0;
	ZOOM_COUNT            = 1;
	APP_RESHAPE_EVENT     = 0;
	ESCAPE1_COLOR_DEF     = 3;
	ESCAPE2_COLOR_DEF     = 0;
	ESCAPE2_THRESHOLD     = 0.03125;
	ESCAPE2_EQUATION      = 0;
	ESCAPE1_COLOR_DAT.exponent = 3.0;
	ESCAPE2_COLOR_DAT.exponent = 17.0;
	ESCAPE2_MIN_ITER      = 1; // never set below 1
	ESCAPE2_MAX_ITER      = MAX_ITERATIONS;
	ESCAPE1_BLEND_FACTOR  = 2.75;
	ESCAPE1_XBAND_FACTOR  = 10.0;
	ESCAPE2_SHADE_FACTOR  = 100.0;
	ITERATION_COUNT[0]    = 0;
	ITERATION_COUNT[1]    = 0;
	ITERATION_COUNT[2]    = 0;
	ITERATION_COUNT[3]    = MAX_ITERATIONS;
	JULIA_MODE            = 0;
	JULIA_X               = -1.0;
	JULIA_Y               = +0.0;
	HYSTERIA_MODE         = 0;
	HYSTERIA_FACTOR       = 64.0;
	
	M_ID[14] = glutCreateMenu(MenuCallback_14); // Julia presets
	
	for(i = 0; i < MAX_JULIA_DEFAULT_PRESETS; i++)
	{
		glutAddMenuEntry(JULIA_DEFAULT_NAMES[i], i+1);
	}
	M_ID[1] = glutCreateMenu(MenuCallback_1); // mode
	glutAddMenuEntry("Mandelbrot Set", 1);
	glutAddSubMenu("Julia Set", M_ID[14]);
	glutAddMenuEntry("Julia/Mandelbrot Hybrid Fractal", 3);
	
	M_ID[2] = glutCreateMenu(MenuCallback_2); // resolution
	glutAddMenuEntry("16x16 blocks", 1);
	glutAddMenuEntry("8x8 blocks", 2);
	glutAddMenuEntry("4x4 blocks", 3);
	glutAddMenuEntry("2x2 blocks", 4);
	glutAddMenuEntry("1x1 blocks", 5);
	
	M_ID[3] = glutCreateMenu(MenuCallback_3); // supersampling
	glutAddMenuEntry("1x1 (off)", 1);
	glutAddMenuEntry("2x2", 2);
	glutAddMenuEntry("4x4", 3);
	glutAddMenuEntry("8x8", 4);
	glutAddMenuEntry("12x12", 4);
	glutAddMenuEntry("Set via console...(1-16)", 99);
	
	M_ID[4] = glutCreateMenu(MenuCallback_4); // focus
	glutAddMenuEntry("0.2 (grainy)", 1);
	glutAddMenuEntry("0.5", 2);
	glutAddMenuEntry("1.0 (perfect)", 3);
	glutAddMenuEntry("1.1 (nice)", 4);
	glutAddMenuEntry("1.5", 5);
	glutAddMenuEntry("2.0", 6);
	glutAddMenuEntry("3.0 (fuzzy)", 7);
	glutAddMenuEntry("Set via console...(0-10)", 99);
	
	M_ID[5] = glutCreateMenu(MenuCallback_5); // iterations
	glutAddMenuEntry("32", 1);
	glutAddMenuEntry("64", 2);
	glutAddMenuEntry("128", 3);
	glutAddMenuEntry("256", 4);
	glutAddMenuEntry("512", 5);
	glutAddMenuEntry("1024", 6);
	glutAddMenuEntry("2048", 7);
	glutAddMenuEntry("Set via console...(4-32768)", 99);
	
	M_ID[6] = glutCreateMenu(MenuCallback_6); // layer-1 color method
	glutAddMenuEntry("None", 1);
	glutAddMenuEntry("Constant Color", 2);
	glutAddMenuEntry("Non-Interpolated Gradient", 3);
	glutAddMenuEntry("Interpolated Gradient", 4); // default
	glutAddMenuEntry("Banded", 5);
	
	M_ID[7] = glutCreateMenu(MenuCallback_7); // layer-1 color gradient
	
	for(i = 0; i < MAX_C_PRESETS; i++)
	{
		glutAddMenuEntry("...", i+1);
	}
	M_ID[8] = glutCreateMenu(MenuCallback_8); // layer-2 color method
	glutAddMenuEntry("None", 1);
	glutAddMenuEntry("Constant Color", 2);
	glutAddMenuEntry("Smooth Shaded", 3);
	glutAddMenuEntry("Reverse Ordered", 4);
	glutAddMenuEntry("Reverse Color Min", 5);
	
	M_ID[9] = glutCreateMenu(MenuCallback_9); // layer-2 color gradient
	
	for(i = 0; i < MAX_C_PRESETS; i++)
	{
		glutAddMenuEntry("...", i+1);
	}
	M_ID[10] = glutCreateMenu(MenuCallback_10); // layer-2 escape equation
	glutAddMenuEntry("x^2 + y^2 \"bubbles\"", 1);
	glutAddMenuEntry("x^2 - y^2 \"spikes1\"", 2);
	glutAddMenuEntry("dx^2 - dy^2 \"spikes2\"", 3);
	glutAddMenuEntry("x dx + y dy \"rings\"", 4);
	glutAddMenuEntry("x dx - y dy \"boomerangs\"", 5);
	glutAddMenuEntry("x dy - y dx \"wires1\"", 6);
	glutAddMenuEntry("x y - dx dy \"wires2\"", 7);
	glutAddMenuEntry("(x^4 + y^4)/(x^2 - y^2) \"clovers\"", 8);
	glutAddMenuEntry("x cos(dy) + y sin(dx) \"spikes3\"", 9);
	glutAddMenuEntry("|x|dx + |y|dy \"twists\"", 10);
	
	M_ID[11] = glutCreateMenu(MenuCallback_11); // advanced options
	glutAddMenuEntry("Set Layer-1 Color Exponent... 3.000", 1);
	glutAddMenuEntry("Set Layer-1 Color Interpolation Factor... 2.750", 2);
	glutAddMenuEntry("Set Layer-1 Color Banding Scale Factor... 10.000", 3);
	glutAddMenuEntry("Set Layer-1 Iteration Bias... ", 4);
	glutAddMenuEntry("Set Layer-2 Color Exponent... 17.000", 5);
	glutAddMenuEntry("Set Layer-2 Color Shade Scale Factor... 100.000", 6);
	glutAddMenuEntry("Set Layer-2 Escape Threshold... 0.0312500", 7);
	glutAddMenuEntry("Set Layer-2 Min Iterations... 1", 8);
	glutAddMenuEntry("Set Layer-2 Max Iterations... 32768", 9);
	glutAddMenuEntry("Set Hysteria Factor... 64.00000", 10);
	glutAddMenuEntry("Reset All Advanced Options", 11);
	
	M_ID[12] = glutCreateMenu(MenuCallback_12); // realtime stat
	glutAddMenuEntry("On", 1);
	glutAddMenuEntry("Off", 2);
	
	M_ID[13] = glutCreateMenu(MenuCallback_13); // Hysteria by James Beattie
	glutAddMenuEntry("On", 1);
	glutAddMenuEntry("Off", 2);
	
	M_ID[0] = glutCreateMenu(MenuCallback_0); // main
	glutAddSubMenu("Mode: Mandelbrot Set", M_ID[1]);
	glutAddSubMenu("Resolution: 1x1", M_ID[2]);
	glutAddSubMenu("Supersampling: 4x4", M_ID[3]);
	glutAddSubMenu("Focus: 1.1", M_ID[4]);
	glutAddSubMenu("Iterations: 256", M_ID[5]);
	glutAddSubMenu("Layer-1 Color Method: Interpolated", M_ID[6]);
	glutAddSubMenu("...", M_ID[7]);
	glutAddSubMenu("Layer-2 Color Method: None", M_ID[8]);
	glutAddSubMenu("...", M_ID[9]);
	glutAddSubMenu("Layer-2 Escape Equation: x^2 + y^2", M_ID[10]);
	glutAddSubMenu("Advanced Options [set via console]", M_ID[11]);
	glutAddSubMenu("Realtime Status Update: On", M_ID[12]);
	glutAddSubMenu("Hysteria by James Beattie: Off", M_ID[13]);
	glutAddMenuEntry("Save Image as *.TGA File...", 1);
	glutAddMenuEntry("Save Image as *.BMP File...", 2);
	glutAddMenuEntry("Save Rendering State...", 3);
	glutAddMenuEntry("Load Rendering State...", 4);
	glutAddMenuEntry("Set Min/Max Coords via Console...", 5);
	glutAddMenuEntry("Reload Color Presets", 6);
	glutAddMenuEntry("Exit", 7);
	
	glutAttachMenu(GLUT_RIGHT_BUTTON);
	
	// **************************************************
	// Color presets must be loaded after menu is created
	// **************************************************
	
	LoadColorPresetList(C_PRESET_PATH);
	LoadColorPreset(&ESCAPE1_COLOR_DAT, C_PRESET_PATH, C_PRESET_NAMES[0]);
	LoadColorPreset(&ESCAPE2_COLOR_DAT, C_PRESET_PATH, C_PRESET_NAMES[3]);
	
	char str[200];
	glutSetMenu(M_ID[0]);
	sprintf(str, "Layer-1 Color Gradient: %s", C_PRESET_NAMES[0]);
	glutChangeToSubMenu(7, str, M_ID[7]);
	sprintf(str, "Layer-2 Color Gradient: %s", C_PRESET_NAMES[3]);
	glutChangeToSubMenu(9, str, M_ID[9]);
	
	Clear();
}

static void Clear(void)
{
	memset(BOX, 0, sizeof(box_t)*2);
	memset(SLICE_BUF, 0, sizeof(pix_t)*VIEW_X*VIEW_Y);
	
	BOX_STATE          = 0;
	BOX_HILITE         = 0;
	BOX_UPDATE         = 0;
	SLICE_POS          = 0;
	SLICE_RES          = MAX_SLICE_RES;
	SLICE_X            = VIEW_X/SLICE_RES;
	SLICE_Y            = VIEW_Y/SLICE_RES;
	SLICE_UPDATE       = 0;
	SLICE_TMP_COUNT    = 0;
	SLICE_HLT_POS      = 0;
	ITERATION_COUNT[0] = 0;
	ITERATION_COUNT[1] = 0;
	ITERATION_COUNT[2] = 0;
	ITERATION_COUNT[3] = MAX_ITERATIONS;
	
	// NOTE: SLICE_HLT_POS serves a dual purpose; to position the hilited
	// scanline above the last rendered slice, and to determine how to
	// unpack pixel data from SLICE_BUF when re-drawing the pixels under
	// the old zoom box.
	
	RENDERING_ENABLED = 1;
	
	DrawStatus(0);
}

static int QFileExists(char *path)
{
	// ***********************************
	// Tests whether a file already exists
	// ***********************************
	
	FILE *test = fopen(path, "rb");
	
	if(test)
	{
		fclose(test);
		return 1;
	}
	return 0;
}

static int QPointInBox(box_t *box, int x, int y)
{
	// ***************************************
	// Determines if mouse click is inside box
	// ***************************************
	
	if(x < _min(box->x1, box->x2)) return 0;
	if(y < _min(box->y1, box->y2)) return 0;
	if(x > _max(box->x1, box->x2)) return 0;
	if(y > _max(box->y1, box->y2)) return 0;
	return 1;
}

static int QDisableRendering(void)
{
	// *********************************************
	// Determine whether rendering should be stopped
	// *********************************************
	
	if(SLICE_POS*SLICE_RES < VIEW_Y) return 0;
	if(SLICE_RES == BLOCK_SIZE)      return 1;
	
	return 0;
}

static void SaveImageAsTGA(char *path)
{
	// ************************
	// Writes image to TGA file
	// ************************
	
	FILE *tga = fopen(path, "wb");
	short data[] = {0,2,0,0,0,0,-1,-1,24};
	
	if(!tga)
	{
		fprintf(stdout, "Can't open file \"%s\"\n", path);
		return;
	}
	// ****************
	// Write TGA header
	// ****************
	
	data[6] = VIEW_X;
	data[7] = VIEW_Y;
	fwrite(data, sizeof(short)*9, 1, tga);
	
	// **************************
	// Allocate buffer for pixels
	// **************************
	
	unsigned char *pix = (unsigned char*)malloc(VIEW_X*VIEW_Y*3);
	if(!pix) exit(-1);
	
	// ************************
	// Redraw view, grab pixels
	// ************************
	
	Redraw(); // gets rid of zoom box, but not slice hilite
	
	glReadPixels(ICON_BAR, STAT_BAR,
	             VIEW_X, VIEW_Y, GL_RGB, GL_UNSIGNED_BYTE, pix);
	
	// ************************
	// Convert BGR->RGB for TGA
	// ************************
	
	for(int i = 0; i < VIEW_X*VIEW_Y; i++)
	{
		pix_t ptmp = pix[i*3+2];
		pix[i*3+2] = pix[i*3];
		pix[i*3]   = ptmp;
	}
	// *************************************
	// Write pixels, close file, free buffer
	// *************************************
	
	fwrite(pix, 1, VIEW_X*VIEW_Y*3, tga);
	fclose(tga);
	free(pix);
}

static void SaveImageAsBMP(char *path)
{
	// ************************
	// Writes image to BMP file
	// ************************
	
	FILE *bmp = fopen(path, "wb");
	char tag[] = "BM";
	long data;
	
	if(!bmp)
	{
		fprintf(stdout, "Can't open file \"%s\"\n", path);
		return;
	}
	// ****************
	// Write BMP header
	// ****************
	
	fwrite(tag, sizeof(char), 2, bmp);
	data = 0x36 + VIEW_X*VIEW_Y*3;
	fwrite(&data, sizeof(long), 1, bmp);
	data = 0;
	fwrite(&data, sizeof(long), 1, bmp);
	data = 0x36;
	fwrite(&data, sizeof(long), 1, bmp);
	data = 0x28;
	fwrite(&data, sizeof(long), 1, bmp);
	data = VIEW_X;
	fwrite(&data, sizeof(long), 1, bmp);
	data = VIEW_Y;
	fwrite(&data, sizeof(long), 1, bmp);
	short sdata = 1;
	fwrite(&sdata, sizeof(short), 1, bmp);
	sdata = 24;
	fwrite(&sdata, sizeof(short), 1, bmp);
	data = 0;
	fwrite(&data, sizeof(long), 1, bmp);
	fwrite(&data, sizeof(long), 1, bmp);
	data = 0xB12;
	fwrite(&data, sizeof(long), 1, bmp);
	fwrite(&data, sizeof(long), 1, bmp);
	data = 0;
	fwrite(&data, sizeof(long), 1, bmp);
	fwrite(&data, sizeof(long), 1, bmp);
	
	// **************************
	// Allocate buffer for pixels
	// **************************
	
	unsigned char *pix = (unsigned char*)malloc(VIEW_X*VIEW_Y*3);
	if(!pix) exit(-1);
	
	// ************************
	// Redraw view, grab pixels
	// ************************
	
	Redraw(); // gets rid of zoom box, but not slice hilite
	
	glReadPixels(ICON_BAR, STAT_BAR,
	             VIEW_X, VIEW_Y, GL_RGB, GL_UNSIGNED_BYTE, pix);
	
	// ************************
	// Convert BGR->RGB for BMP
	// ************************
	
	for(int i = 0; i < VIEW_X*VIEW_Y; i++)
	{
		pix_t ptmp = pix[i*3+2];
		pix[i*3+2] = pix[i*3];
		pix[i*3]   = ptmp;
	}
	// *************************************
	// Write pixels, close file, free buffer
	// *************************************
	
	fwrite(pix, 1, VIEW_X*VIEW_Y*3, bmp);
	
	fclose(bmp);
	free(pix);
}

static void MenuCallback_0(int id)
{
	char path[200];
	
	if(id < 5)
	{
		fprintf(stdout, "Enter filename: ");
		fgets(path, 199, stdin);
		{
			if(path[0] == '\n')
			{
				fprintf(stdout, "Canceled.\n");
				return;
			}
			char *ret = strchr(path, '\n');
			if(ret) *ret = '\0'; // remove newline
		}
	}
	if(id == 1)
	{
		// *****************
		// Save image as TGA
		// *****************
		
		if(!strstr(path, ".tga") && !strstr(path, ".TGA"))
		{
			strcat(path, ".tga");
		}
		if(QFileExists(path))
		{
			char yes_no[200];
			fprintf(stdout, "File already exists. Overwrite? (y/n): ");
			fgets(yes_no, 199, stdin);
			
			if(yes_no[0] != 'y' && yes_no[0] != 'Y')
			{
				fprintf(stdout, "Canceled.\n");
				return;
			}
		}
		SaveImageAsTGA(path);
		
		fprintf(stdout, "Saved.\n");
	}
	if(id == 2)
	{
		// *****************
		// Save image as BMP
		// *****************
		
		if(!strstr(path, ".bmp") && !strstr(path, ".BMP"))
		{
			strcat(path, ".bmp");
		}
		if(QFileExists(path))
		{
			char yes_no[200];
			fprintf(stdout, "File already exists. Overwrite? (y/n): ");
			fgets(yes_no, 199, stdin);
			
			if(yes_no[0] != 'y' && yes_no[0] != 'Y')
			{
				fprintf(stdout, "Canceled.\n");
				return;
			}
		}
		SaveImageAsBMP(path);
		
		fprintf(stdout, "Saved.\n");
	}
	else if(id == 3)
	{
		// ********************
		// Save rendering state
		// ********************
		
		if(QFileExists(path))
		{
			char yes_no[200];
			fprintf(stdout, "File already exists. Overwrite? (y/n): ");
			fgets(yes_no, 199, stdin);
			
			if(yes_no[0] != 'y' && yes_no[0] != 'Y')
			{
				fprintf(stdout, "Canceled.\n");
				return;
			}
		}
		FILE *rstate = fopen(path, "wb");
		
		if(!rstate)
		{
			fprintf(stdout, "Can't open file\"%s\"\n", path);
			return;
		}
		// ************************************************
		// 30+ global variables is a BITCH to keep track of
		// ************************************************
		
		int version = VERSION;
		fwrite(&version, sizeof(int), 1, rstate);
		fwrite(&VIEW_X, sizeof(int), 1, rstate);
		fwrite(&VIEW_Y, sizeof(int), 1, rstate);
		fwrite(&SLICE_POS, sizeof(int), 1, rstate);
		fwrite(&SLICE_HLT_POS, sizeof(int), 1, rstate);
		fwrite(&SLICE_RES, sizeof(int), 1, rstate);
		fwrite(&BLOCK_SIZE, sizeof(int), 1, rstate);
		fwrite(&SSAMP_SIZE, sizeof(int), 1, rstate);
		SaveSliceBuffer(rstate);
		fwrite(&ITERATION_COUNT[1], sizeof(int), 1, rstate);
		fwrite(&ITERATION_COUNT[2], sizeof(int), 1, rstate);
		fwrite(&ITERATION_COUNT[3], sizeof(int), 1, rstate);
		fwrite(&JULIA_MODE, sizeof(int), 1, rstate);
		fwrite(&JULIA_X, sizeof(double), 1, rstate);
		fwrite(&JULIA_Y, sizeof(double), 1, rstate);
		fwrite(&JULIA_ZOOM_INDEX, sizeof(int), 1, rstate);
		fwrite(&ESCAPE1_COLOR_DEF, sizeof(int), 1, rstate);
		fwrite(&ESCAPE2_COLOR_DEF, sizeof(int), 1, rstate);
		int adj = sizeof(pix_t)*(MAX_ITERATIONS+1);
		fwrite(&ESCAPE1_COLOR_DAT, sizeof(cdat_t)-adj, 1, rstate);
		fwrite(&ESCAPE2_COLOR_DAT, sizeof(cdat_t)-adj, 1, rstate);
		fwrite(&ESCAPE2_THRESHOLD, sizeof(double), 1, rstate);
		fwrite(&ESCAPE2_EQUATION, sizeof(int), 1, rstate);
		fwrite(&ESCAPE2_MIN_ITER, sizeof(int), 1, rstate);
		fwrite(&ESCAPE2_MAX_ITER, sizeof(int), 1, rstate);
		fwrite(&ESCAPE1_BLEND_FACTOR, sizeof(double), 1, rstate);
		fwrite(&ESCAPE1_XBAND_FACTOR, sizeof(double), 1, rstate);
		fwrite(&ESCAPE2_SHADE_FACTOR, sizeof(double), 1, rstate);
		fwrite(&ITERATIONS_PER_PIXEL, sizeof(int), 1, rstate);
		fwrite(&HYSTERIA_MODE, sizeof(int), 1, rstate);
		fwrite(&HYSTERIA_FACTOR, sizeof(double), 1, rstate);
		fwrite(&FOCUS, sizeof(double), 1, rstate);
		//fwrite(&REALTIME_STAT_UPDATE, sizeof(int), 1, rstate);
		fwrite(&ZOOM_COUNT, sizeof(int), 1, rstate);
		fwrite(ZOOM_LIST, sizeof(zoom_t), ZOOM_COUNT, rstate);
		fclose(rstate);
		
		fprintf(stdout, "Saved.\n");
	}
	else if(id == 4)
	{
		// ********************
		// Load rendering state
		// ********************
		
		FILE *rstate = fopen(path, "rb");
		
		if(!rstate)
		{
			fprintf(stdout, "Can't open file\"%s\"\n", path);
			return;
		}
		int version;
		fread(&version, sizeof(int), 1, rstate);
		
		if(version != VERSION)
		{
			fprintf(stdout, "Incompatible versions!\n");
			fclose(rstate);
			return;
		}
		fread(&VIEW_X, sizeof(int), 1, rstate);
		fread(&VIEW_Y, sizeof(int), 1, rstate);
		fread(&SLICE_POS, sizeof(int), 1, rstate);
		fread(&SLICE_HLT_POS, sizeof(int), 1, rstate);
		fread(&SLICE_RES, sizeof(int), 1, rstate);
		fread(&BLOCK_SIZE, sizeof(int), 1, rstate);
		fread(&SSAMP_SIZE, sizeof(int), 1, rstate);
		SLICE_X = VIEW_X/SLICE_RES;
		SLICE_Y = VIEW_Y/SLICE_RES;
		LoadSliceBuffer(rstate);
		fread(&ITERATION_COUNT[1], sizeof(int), 1, rstate);
		fread(&ITERATION_COUNT[2], sizeof(int), 1, rstate);
		fread(&ITERATION_COUNT[3], sizeof(int), 1, rstate);
		fread(&JULIA_MODE, sizeof(int), 1, rstate); // update MENU0:1
		fread(&JULIA_X, sizeof(double), 1, rstate);
		fread(&JULIA_Y, sizeof(double), 1, rstate);
		fread(&JULIA_ZOOM_INDEX, sizeof(int), 1, rstate);
		fread(&ESCAPE1_COLOR_DEF, sizeof(int), 1, rstate); // update MENU0:6
		fread(&ESCAPE2_COLOR_DEF, sizeof(int), 1, rstate); // update MENU0:8
		int adj = sizeof(pix_t)*(MAX_ITERATIONS+1);
		fread(&ESCAPE1_COLOR_DAT, sizeof(cdat_t)-adj, 1, rstate); // 0:7
		fread(&ESCAPE2_COLOR_DAT, sizeof(cdat_t)-adj, 1, rstate); // 0:9
		RecomputeColorGradient(&ESCAPE1_COLOR_DAT);
		RecomputeColorGradient(&ESCAPE2_COLOR_DAT);
		fread(&ESCAPE2_THRESHOLD, sizeof(double), 1, rstate); // 11:
		fread(&ESCAPE2_EQUATION, sizeof(int), 1, rstate); // 0:10
		fread(&ESCAPE2_MIN_ITER, sizeof(int), 1, rstate); // 11:
		fread(&ESCAPE2_MAX_ITER, sizeof(int), 1, rstate); // 11:
		fread(&ESCAPE1_BLEND_FACTOR, sizeof(double), 1, rstate); // 11:
		fread(&ESCAPE1_XBAND_FACTOR, sizeof(double), 1, rstate); // 11:
		fread(&ESCAPE2_SHADE_FACTOR, sizeof(double), 1, rstate); // 11:
		fread(&ITERATIONS_PER_PIXEL, sizeof(int), 1, rstate); // 
		fread(&HYSTERIA_MODE, sizeof(int), 1, rstate); // 
		fread(&HYSTERIA_FACTOR, sizeof(double), 1, rstate); // 11:
		fread(&FOCUS, sizeof(double), 1, rstate); // 
		//fread(&REALTIME_STAT_UPDATE, sizeof(int), 1, rstate);
		fread(&ZOOM_COUNT, sizeof(int), 1, rstate);
		fread(ZOOM_LIST, sizeof(zoom_t), ZOOM_COUNT, rstate);
		fclose(rstate);
		
		// ************
		// Update menus
		// ************
		
		UpdateMenu_1();
		UpdateMenu_2();
		UpdateMenu_3();
		UpdateMenu_4();
		UpdateMenu_5();
		UpdateMenu_6();
		UpdateMenu_7();
		UpdateMenu_8();
		UpdateMenu_9();
		UpdateMenu_10();
		UpdateMenu_11();
		UpdateMenu_13();
		
		// ***************************
		// Reconstruct other variables
		// ***************************
		
		int old_win_x = WINDOW_X;
		int old_win_y = WINDOW_Y;
		
		WINDOW_X = VIEW_X + ICON_BAR;
		WINDOW_Y = VIEW_Y + STAT_BAR;
		SLICE_UPDATE = 0;
		SLICE_TMP_COUNT = 0;
		
		if(WINDOW_X != old_win_x || WINDOW_Y != old_win_y)
		{
			// *********************************************
			// Window will be redrawn inside GLUT__Reshape()
			// *********************************************
			
			APP_RESHAPE_EVENT = 1;
			glutReshapeWindow(WINDOW_X, WINDOW_Y);
		}
		else
		{
			// ***************************************
			// Window doesn't need reshaping - so just
			// redraw it here..
			// ***************************************
			
			Redraw();
		}
	}
	else if(id == 5)
	{
		// ***********************************
		// Set min/max coordinates via console
		// ***********************************
		
		char str[200];
		double q[4], bounds[4];
		
		for(int i = 0; i < 4; i++)
		{
			switch(i)
			{
				case 0: fprintf(stdout, "Enter X Min: "); break;
				case 1: fprintf(stdout, "Enter Y Min: "); break;
				case 2: fprintf(stdout, "Enter X Max: "); break;
				case 3: fprintf(stdout, "Enter Y Max: "); break;
			}
			fgets(str, 199, stdin);
			if(str[0] == '\n')
			{
				fprintf(stdout, "Canceled.\n");
				return;
			}
			q[i] = atof(str);
		}
		bounds[0] = _max(_min(q[0], q[2]), -3.0);
		bounds[1] = _max(_min(q[1], q[3]), -3.0);
		bounds[2] = _min(_max(q[0], q[2]), +3.0);
		bounds[3] = _min(_max(q[1], q[3]), +3.0);
		
		if(bounds[0] == bounds[2] || bounds[1] == bounds[3])
		{
			fprintf(stdout, "Invalid bounds!\n");
			return;
		}
		ZOOM_LIST[ZOOM_COUNT].x =     -(bounds[0] + bounds[2])/2.0;
		ZOOM_LIST[ZOOM_COUNT].y =     -(bounds[1] + bounds[3])/2.0;
		ZOOM_LIST[ZOOM_COUNT].z = -2.0/(bounds[1] - bounds[3]);
		ZOOM_COUNT++;
		Clear();
	}
	else if(id == 6)
	{
		LoadColorPresetList(C_PRESET_PATH);
		LoadColorPreset(&ESCAPE1_COLOR_DAT, C_PRESET_PATH,
			ESCAPE1_COLOR_DAT.name);
		LoadColorPreset(&ESCAPE2_COLOR_DAT, C_PRESET_PATH,
			ESCAPE2_COLOR_DAT.name);
	}
	else if(id == 7) exit(0);
}

static void MenuCallback_14(int id) // Julia presets
{
	char str[200];
	double jx, jy;
	
	if(id-1 < MAX_JULIA_DEFAULT_PRESETS-1)
	{
		jx = JULIA_DEFAULT_COORDS[(id-1)*2+0];
		jy = JULIA_DEFAULT_COORDS[(id-1)*2+1];
	}
	else
	{
		fprintf(stdout, "Enter Julia X: ");
		fgets(str, 199, stdin);
		
		if(str[0] == '\n')
		{
			fprintf(stdout, "Canceled.\n");
			return;
		}
		jx = atof(str);
		fprintf(stdout, "Enter Julia Y: ");
		fgets(str, 199, stdin);
		
		if(str[0] == '\n')
		{
			fprintf(stdout, "Canceled.\n");
			return;
		}
		jy = atof(str);
	}
	if(jx != JULIA_X || jy != JULIA_Y || JULIA_MODE != 1)
	{
		JULIA_X = jx;
		JULIA_Y = jy;
		
		if(JULIA_MODE)
		{
			// **********
			// Reset zoom
			// **********
			
			ZOOM_COUNT = JULIA_ZOOM_INDEX;
		}
		JULIA_ZOOM_INDEX = ZOOM_COUNT;
		JULIA_MODE = 1;
		ZOOM_LIST[ZOOM_COUNT].x = 0.00;
		ZOOM_LIST[ZOOM_COUNT].y = 0.00;
		ZOOM_LIST[ZOOM_COUNT].z = 0.75;
		ZOOM_LIST[ZOOM_COUNT].iteration_bias = 0;
		ZOOM_COUNT++;
		Clear();
	}
	glutSetMenu(M_ID[0]);
	glutChangeToSubMenu(1, "Mode: Julia Set", M_ID[1]);
}

static void UpdateMenu_1(void) // mode
{
	glutSetMenu(M_ID[0]);
	
	switch(JULIA_MODE)
	{
		case 0:
			glutChangeToSubMenu(1, "Mode: Mandelbrot Set", M_ID[1]);
			break;
		case 1:
			glutChangeToSubMenu(1, "Mode: Julia Set", M_ID[1]);
			break;
		case 2:
			glutChangeToSubMenu(1,
			"Mode: Julia/Mandelbrot Hybrid Fractal", M_ID[1]);
			break;
	}
}

static void MenuCallback_1(int id) // mode
{
	if(id == 1 && JULIA_MODE != 0)
	{
		JULIA_MODE = 0;
		ZOOM_COUNT = JULIA_ZOOM_INDEX;
		UpdateMenu_1();
		Clear();
	}
	else if(id == 3 && JULIA_MODE != 2)
	{
		if(JULIA_MODE)
		{
			// **********
			// Reset zoom
			// **********
			
			ZOOM_COUNT = JULIA_ZOOM_INDEX;
		}
		JULIA_ZOOM_INDEX = ZOOM_COUNT;
		JULIA_MODE = 2;
		ZOOM_LIST[ZOOM_COUNT].x = 0.00;
		ZOOM_LIST[ZOOM_COUNT].y = 0.00;
		ZOOM_LIST[ZOOM_COUNT].z = 0.75;
		ZOOM_LIST[ZOOM_COUNT].iteration_bias = 0;
		ZOOM_COUNT++;
		UpdateMenu_1();
		Clear();
	}
}

static void UpdateMenu_2(void) // resolution
{
	char str[200];
	
	sprintf(str, "Resolution: %ix%i", BLOCK_SIZE, BLOCK_SIZE);
	glutSetMenu(M_ID[0]);
	glutChangeToSubMenu(2, str, M_ID[2]);
}

static void MenuCallback_2(int id) // resolution
{
	int vtab[] = {16, 8, 4, 2, 1};
	
	if(BLOCK_SIZE != vtab[id-1])
	{
		BLOCK_SIZE = vtab[id-1];
		UpdateMenu_2();
		Clear();
	}
}

static void UpdateMenu_3(void) // supersampling
{
	char str[200];
	
	if(SSAMP_SIZE == 1) strcpy(str, "Supersampling: Off");
	else sprintf(str, "Supersampling: %ix%i", SSAMP_SIZE, SSAMP_SIZE);
	glutSetMenu(M_ID[0]);
	glutChangeToSubMenu(3, str, M_ID[3]);
}

static void MenuCallback_3(int id) // supersampling
{
	int vtab[] = {1, 2, 4, 8, 12};
	int result;
	
	if(id == 99)
	{
		char s[200];
		fprintf(stdout, "Enter supersampling size (1-16): ");
		fgets(s, 199, stdin);
		if(strlen(s) < 1) return; // nothing typed
		result = atoi(s);
		if(result < 1)  result = 1;
		if(result > 16) result = 16;
	}
	else result = vtab[id-1];
	
	if(SSAMP_SIZE != result)
	{
		SSAMP_SIZE = result;
		UpdateMenu_3();
		Clear();
	}
}

static void UpdateMenu_4(void) // focus
{
	char str[200];
	
	sprintf(str, "Focus: %.2f", FOCUS);
	glutSetMenu(M_ID[0]);
	glutChangeToSubMenu(4, str, M_ID[4]);
}

static void MenuCallback_4(int id) // focus
{
	double vtab[] = {0.2, 0.5, 1.0, 1.1, 1.5, 2.0, 3.0};
	double result;
	
	if(id == 99)
	{
		char s[200];
		fprintf(stdout, "Enter focus (0-10): ");
		fgets(s, 199, stdin);
		if(strlen(s) < 1) return; // nothing typed
		result = atof(s);
		if(result <  0.0) result =  0.0;
		if(result > 10.0) result = 10.0;
	}
	else result = vtab[id-1];
	
	if(FOCUS != result)
	{
		FOCUS = result;
		UpdateMenu_4();
		Clear();
	}
}

static void UpdateMenu_5(void) // iterations
{
	char str[200];
	
	sprintf(str, "Iterations: %i", ITERATIONS_PER_PIXEL);
	glutSetMenu(M_ID[0]);
	glutChangeToSubMenu(5, str, M_ID[5]);
}

static void MenuCallback_5(int id) // iterations
{
	int vtab[] = {32, 64, 128, 256, 512, 1024, 2048};
	int result;
	
	if(id == 99)
	{
		char s[200];
		fprintf(stdout, "Enter max iterations per pixel (4-32768): ");
		fgets(s, 199, stdin);
		if(strlen(s) < 1) return; // nothing typed
		result = atoi(s);
		if(result < 4) result = 4;
		if(result > 32768) result = 32768;
	}
	else result = vtab[id-1];
	
	if(ITERATIONS_PER_PIXEL != result)
	{
		ITERATIONS_PER_PIXEL = result;
		UpdateMenu_5();
		Clear();
	}
}

static void UpdateMenu_6(void) // layer-1 color method
{
	char str[200];
	
	switch(ESCAPE1_COLOR_DEF)
	{
		case 0: strcpy(str, "Layer-1 Color Method: None"); break;
		case 1: strcpy(str, "Layer-1 Color Method: Constant Color"); break;
		case 2: strcpy(str, "Layer-1 Color Method: Non-Interpolated"); break;
		case 3: strcpy(str, "Layer-1 Color Method: Interpolated"); break;
		case 4: strcpy(str, "Layer-1 Color Method: Banded"); break;
	}
	glutSetMenu(M_ID[0]);
	glutChangeToSubMenu(6, str, M_ID[6]);
}

static void MenuCallback_6(int id) // layer-1 color method
{
	if(ESCAPE1_COLOR_DEF != id-1)
	{
		ESCAPE1_COLOR_DEF = id-1;
		UpdateMenu_6();
		Clear();
	}
}

static void UpdateMenu_7(void) // layer-1 color gradient
{
	char str[200];
	
	glutSetMenu(M_ID[0]);
	sprintf(str, "Layer-1 Color Gradient: %s", ESCAPE1_COLOR_DAT.name);
	glutChangeToSubMenu(7, str, M_ID[7]);
}

static void MenuCallback_7(int id) // layer-1 color gradient
{
	char *name = C_PRESET_NAMES[id-1];
	if(id > C_PRESET_COUNT) return;
	if(!strcmp(ESCAPE1_COLOR_DAT.name, name)) return;
	
	LoadColorPreset(&ESCAPE1_COLOR_DAT, C_PRESET_PATH, name);
	UpdateMenu_7();
	Clear();
}

static void UpdateMenu_8(void) // layer-2 color method
{
	char str[200];
	
	switch(ESCAPE2_COLOR_DEF)
	{
		case 0: strcpy(str, "Layer-2 Color Method: None"); break;
		case 1: strcpy(str, "Layer-2 Color Method: Constant Color"); break;
		case 2: strcpy(str, "Layer-2 Color Method: Smooth Shaded"); break;
		case 3: strcpy(str, "Layer-2 Color Method: Reverse Ordered"); break;
		case 4: strcpy(str, "Layer-2 Color Method: Reverse Color Min"); break;
	}
	glutSetMenu(M_ID[0]);
	glutChangeToSubMenu(8, str, M_ID[8]);
}

static void MenuCallback_8(int id) // layer-2 color method
{
	if(ESCAPE2_COLOR_DEF != id-1)
	{
		ESCAPE2_COLOR_DEF = id-1;
		UpdateMenu_8();
		Clear();
	}
}

static void UpdateMenu_9(void) // layer-1 color gradient
{
	char str[200];
	
	glutSetMenu(M_ID[0]);
	sprintf(str, "Layer-2 Color Gradient: %s", ESCAPE2_COLOR_DAT.name);
	glutChangeToSubMenu(9, str, M_ID[9]);
}

static void MenuCallback_9(int id) // layer-2 color gradient
{
	char *name = C_PRESET_NAMES[id-1];
	if(id > C_PRESET_COUNT) return;
	if(!strcmp(ESCAPE2_COLOR_DAT.name, name)) return;
	
	LoadColorPreset(&ESCAPE2_COLOR_DAT, C_PRESET_PATH, name);
	UpdateMenu_9();
	Clear();
}

static void UpdateMenu_10(void) // layer-2 escape equation
{
	char str[200];
	
	switch(ESCAPE2_EQUATION)
	{
		case 0: strcpy(str, "Layer-2 Escape Equation: x^2 + y^2");   break;
		case 1: strcpy(str, "Layer-2 Escape Equation: x^2 - y^2");   break;
		case 2: strcpy(str, "Layer-2 Escape Equation: dx^2 - dy^2"); break;
		case 3: strcpy(str, "Layer-2 Escape Equation: x dx + y dy"); break;
		case 4: strcpy(str, "Layer-2 Escape Equation: x dx - y dy"); break;
		case 5: strcpy(str, "Layer-2 Escape Equation: x dy - y dx"); break;
		case 6: strcpy(str, "Layer-2 Escape Equation: x y - dx dy"); break;
		case 7: strcpy(str,
		        "Layer-2 Escape Equation: (x^4 + y^4)/(x^2 - y^2)"); break;
		case 8: strcpy(str,
		        "Layer-2 Escape Equation: x cos(dy) + y sin(dx)"); break;
		case 9: strcpy(str,
		        "Layer-2 Escape Equation: |x|dx + |y|dy"); break;
	}
	glutSetMenu(M_ID[0]);
	glutChangeToSubMenu(10, str, M_ID[10]);
}

static void MenuCallback_10(int id) // layer-2 escape equation
{	
	if(ESCAPE2_EQUATION != id-1)
	{
		ESCAPE2_EQUATION = id-1;
		UpdateMenu_10();
		if(ESCAPE2_COLOR_DEF) Clear();
	}
}

static void UpdateMenu_11(void) // advanced options
{
	char str[200];
	
	glutSetMenu(M_ID[11]);
	
	sprintf(str, "Set Layer-1 Color Exponent... %.3f",
	ESCAPE1_COLOR_DAT.exponent);
	glutChangeToMenuEntry(1, str, 1);
	sprintf(str,
	"Set Layer-1 Color Interpolation Factor... %.3f",
	ESCAPE1_BLEND_FACTOR);
	glutChangeToMenuEntry(2, str, 2);
	sprintf(str,
	"Set Layer-1 Color Banding Scale Factor... %.3f",
	ESCAPE1_XBAND_FACTOR);
	glutChangeToMenuEntry(3, str, 3);
	sprintf(str, "Set Layer-2 Color Exponent... %.3f",
	ESCAPE2_COLOR_DAT.exponent);
	glutChangeToMenuEntry(5, str, 5);
	sprintf(str,
	"Set Layer-2 Color Shade Scale Factor... %.3f",
	ESCAPE2_SHADE_FACTOR);
	glutChangeToMenuEntry(6, str, 6);
	sprintf(str, "Set Layer-2 Escape Threshold... %.7f",
	ESCAPE2_THRESHOLD);
	glutChangeToMenuEntry(7, str, 7);
	sprintf(str, "Set Layer-2 Min Iterations... %i",
	ESCAPE2_MIN_ITER);
	glutChangeToMenuEntry(8, str, 8);
	sprintf(str, "Set Layer-2 Max Iterations... %i",
	ESCAPE2_MAX_ITER);
	glutChangeToMenuEntry(9, str, 9);
	sprintf(str, "Set Hysteria Factor... %.5f",
	HYSTERIA_FACTOR);
	glutChangeToMenuEntry(10, str, 10);
}

static void MenuCallback_11(int id) // advanced options
{
	char str[200];
	
	glutSetMenu(M_ID[11]);
	
	if(id <= 10)
	{
		fprintf(stdout, "Enter value: ");
		fgets(str, 199, stdin);
		
		if(str[0] == '\n')
		{
			fprintf(stdout, "Canceled.\n");
			return;
		}
		if(id == 1)
		{
			ESCAPE1_COLOR_DAT.exponent = atof(str);
			RecomputeColorGradient(&ESCAPE1_COLOR_DAT);
			sprintf(str, "Set Layer-1 Color Exponent... %.3f", atof(str));
			glutChangeToMenuEntry(1, str, 1);
		}
		else if(id == 2)
		{
			ESCAPE1_BLEND_FACTOR = atof(str);
			sprintf(str,
			"Set Layer-1 Color Interpolation Factor... %.3f", atof(str));
			glutChangeToMenuEntry(2, str, 2);
		}
		else if(id == 3)
		{
			ESCAPE1_XBAND_FACTOR = atof(str);
			sprintf(str,
			"Set Layer-1 Color Banding Scale Factor... %.3f", atof(str));
			glutChangeToMenuEntry(3, str, 3);
		}
		else if(id == 4)
		{
			if(str[0] == '?')
			{
				fprintf(stdout, "Iteration Bias= %i\n",
				ZOOM_LIST[ZOOM_COUNT-1].iteration_bias);
			}
			else
			{
				//WARNING: very dangerous!
				ZOOM_LIST[ZOOM_COUNT-1].iteration_bias = atoi(str);
			}
		}
		else if(id == 5)
		{
			ESCAPE2_COLOR_DAT.exponent = atof(str);
			RecomputeColorGradient(&ESCAPE2_COLOR_DAT);
			sprintf(str, "Set Layer-2 Color Exponent... %.3f", atof(str));
			glutChangeToMenuEntry(5, str, 5);
		}
		else if(id == 6)
		{
			ESCAPE2_SHADE_FACTOR = atof(str);
			sprintf(str,
			"Set Layer-2 Color Shade Scale Factor... %.3f", atof(str));
			glutChangeToMenuEntry(6, str, 6);
		}
		else if(id == 7)
		{
			ESCAPE2_THRESHOLD = atof(str);
			sprintf(str, "Set Layer-2 Escape Threshold... %.7f", atof(str));
			glutChangeToMenuEntry(7, str, 7);
		}
		else if(id == 8)
		{
			ESCAPE2_MIN_ITER = atoi(str);
			sprintf(str, "Set Layer-2 Min Iterations... %i", atoi(str));
			glutChangeToMenuEntry(8, str, 8);
		}
		else if(id == 9)
		{
			ESCAPE2_MAX_ITER = atoi(str);
			sprintf(str, "Set Layer-2 Max Iterations... %i", atoi(str));
			glutChangeToMenuEntry(9, str, 9);
		}
		else if(id == 10)
		{
			HYSTERIA_FACTOR = atof(str);
			sprintf(str, "Set Hysteria Factor... %.5f", atof(str));
			glutChangeToMenuEntry(10, str, 10);
		}
	}
	else if(id == 11)
	{
		ESCAPE1_COLOR_DAT.exponent = 3.0;
		ESCAPE1_BLEND_FACTOR = 2.75;
		ESCAPE1_XBAND_FACTOR = 10.0;
		ESCAPE2_COLOR_DAT.exponent = 17.0;
		ESCAPE2_SHADE_FACTOR = 100.0;
		ESCAPE2_THRESHOLD = 0.03125;
		ESCAPE2_MIN_ITER = 1;
		ESCAPE2_MAX_ITER = MAX_ITERATIONS;
		HYSTERIA_FACTOR = 64.0;
		RecomputeColorGradient(&ESCAPE1_COLOR_DAT);
		RecomputeColorGradient(&ESCAPE2_COLOR_DAT);
		
		glutChangeToMenuEntry(1,
		"Set Layer-1 Color Exponent... 3.000", 1);
		glutChangeToMenuEntry(2,
		"Set Layer-1 Color Interpolation Factor... 2.750", 2);
		glutChangeToMenuEntry(3,
		"Set Layer-1 Color Banding Scale Factor... 10.000", 3);
		glutChangeToMenuEntry(4,
		"Set Layer-1 Iteration Bias... ", 4);
		glutChangeToMenuEntry(5,
		"Set Layer-2 Color Exponent... 17.000", 5);
		glutChangeToMenuEntry(6,
		"Set Layer-2 Color Shade Scale Factor... 100.000", 6);
		glutChangeToMenuEntry(7,
		"Set Layer-2 Escape Threshold... 0.0312500", 7);
		glutChangeToMenuEntry(8,
		"Set Layer-2 Min Iterations... 1", 8);
		glutChangeToMenuEntry(9,
		"Set Layer-2 Max Iterations... 32768", 9);
		glutChangeToMenuEntry(10,
		"Set Hysteria Factor... 64.00000", 10);
	}
	Clear();
}

static void MenuCallback_12(int id) // realtime stat
{
	char str[200];
	
	if(id == 1)
	{
		sprintf(str, "Realtime Status Update: On");
		REALTIME_STAT_UPDATE = 1;
	}
	else
	{
		sprintf(str, "Realtime Status Update: Off");
		REALTIME_STAT_UPDATE = 0;
	}
	glutSetMenu(M_ID[0]);
	glutChangeToSubMenu(12, str, M_ID[12]);
}

static void UpdateMenu_13(void) // Hysteria by James Beattie
{
	char str[200];
	
	if(HYSTERIA_MODE)
	{
		sprintf(str, "Hysteria by James Beattie: On");
	}
	else
	{
		sprintf(str, "Hysteria by James Beattie: Off");
	}
	glutSetMenu(M_ID[0]);
	glutChangeToSubMenu(13, str, M_ID[13]);
}

static void MenuCallback_13(int id) // Hysteria by James Beattie
{
	if(id == 1 && HYSTERIA_MODE == 0)
	{
		HYSTERIA_MODE = 1;
		UpdateMenu_13();
		Clear();
	}
	else if(id == 2 && HYSTERIA_MODE == 1)
	{
		HYSTERIA_MODE = 0;
		UpdateMenu_13();
		Clear();
	}
}

static void RecomputeColorGradient(cdat_t *cdat)
{
	for(int k = 1, i = 0; i <= MAX_ITERATIONS; i++)
	{
		double t = pow(1.0 - 1.0/(double)(i+1), cdat->exponent);
		while(t >= cdat->colors[k*4]) k++;
		
		t = (cdat->colors[k*4] - t) /
		    (cdat->colors[k*4] - cdat->colors[(k-1)*4]);
		
		double r1 = cdat->colors[(k-1)*4+1]*t;
		double g1 = cdat->colors[(k-1)*4+2]*t;
		double b1 = cdat->colors[(k-1)*4+3]*t;
		double r2 = cdat->colors[k*4+1]*(1.0-t);
		double g2 = cdat->colors[k*4+2]*(1.0-t);
		double b2 = cdat->colors[k*4+3]*(1.0-t);
		
		pix_t r = (pix_t)((r1+r2)*255.0);
		pix_t g = (pix_t)((g1+g2)*255.0);
		pix_t b = (pix_t)((b1+b2)*255.0);
		
		cdat->table[i] = r|(g<<8)|(b<<16);
	}
}

static void LoadColorPreset(cdat_t *cdat, char *path, char *name)
{
	FILE *cprefs = fopen(path, "r");
	char str[200];
	
	if(!cprefs)
	{
		fprintf(stdout, "Can't open file \"%s\"\n", path);
		return;
	}
	while(fgets(str, 199, cprefs))
	{
		if(str[0] == '#') continue;
		if(str[0] == '/' && str[1] == '/') continue;
		if(str[0] == '\"' && !strncmp(str+1, name, strlen(name)))
		{
			cdat->count = atoi(str + strlen(name) + 3);
			
			for(int k = 0; k < cdat->count; k++)
			{
				fgets(str, 199, cprefs);
				sscanf(str, "%lf %lf %lf %lf\n",
					&cdat->colors[k*4+0],
					&cdat->colors[k*4+1],
					&cdat->colors[k*4+2],
					&cdat->colors[k*4+3]);
			}
			RecomputeColorGradient(cdat);
			strncpy(cdat->name, name, 47);
			break;
		}
	}
	fclose(cprefs);
}

static void LoadColorPresetList(char *path)
{
	FILE *cprefs = fopen(path, "rb");
	char str[200];
	int i;
	
	if(!cprefs)
	{
		fprintf(stdout, "Can't open file \"%s\"\n", path);
		return;
	}
	C_PRESET_COUNT = 0;
	
	while(fgets(str, 199, cprefs))
	{
		if(str[0] == '#') continue;
		if(str[0] == '/' && str[1] == '/') continue;
		if(str[0] == '\"')
		{
			char *quot = strchr(str+1, '\"');
			if(!quot) continue;
			int len = _min(quot-str-1, 47);
			memmove(str, str+1, len);
			str[len] = '\0';
			
			strcpy(C_PRESET_NAMES[C_PRESET_COUNT++], str);

			if(C_PRESET_COUNT == MAX_C_PRESETS)
			{
				break;
			}
		}
	}
	fclose(cprefs);
	
	glutSetMenu(M_ID[7]);
	
	for(i = 0; i < MAX_C_PRESETS; i++)
	{
		if(i < C_PRESET_COUNT)
		{
			glutChangeToMenuEntry(i+1, C_PRESET_NAMES[i], i+1);
		}
		else
		{
			glutChangeToMenuEntry(i+1, "<...>", i+1);
		}
	}
	glutSetMenu(M_ID[9]);
	
	for(i = 0; i < MAX_C_PRESETS; i++)
	{
		if(i < C_PRESET_COUNT)
		{
			glutChangeToMenuEntry(i+1, C_PRESET_NAMES[i], i+1);
		}
		else
		{
			glutChangeToMenuEntry(i+1, "<...>", i+1);
		}
	}
}

static void SetPixelCoords(int lft, int bot, int rgt, int top, int clear)
{
	glViewport(lft, bot, rgt-lft, top-bot);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glOrtho(0.f, (float)(rgt-lft), 0.f, (float)(top-bot), 1.f, -1.f);
	
	if(clear)
	{
		glRecti(0, 0, rgt-lft, top-bot);
	}
}

static void Pix2WorldCoords(double &x, double &y)
{
	// **********************************************
	// Convert pixel-coordinates to world-coordinates
	// **********************************************
	
	double vx = (double)(VIEW_X/2);
	double vy = (double)(VIEW_Y/2);
	zoom_t *z = &ZOOM_LIST[ZOOM_COUNT-1];
	
	x = (x-vx)/(vy*z->z) - z->x;
	y = (y-vy)/(vy*z->z) - z->y;
}

static void DrawText(int x, int y, char *str)
{
	glRasterPos2i(x, y);
	
	while(*str != '\0')
	{
		glutBitmapCharacter(GLUT_BITMAP_8_BY_13, *(str++));
	}
}

static void IncrementCount(int dcount)
{
	// **********************************************
	// Increment iteration counters. There are
	// three 32-bit signed values:
	//   [0]: local counter for managing GUI updates
	//   [1]: low-order 9 digits
	//   [2]: high-order 9 digits (total = 18 digits)
	//   [3]: local min-iter counter for current view,
	//        used for ZOOM->iteration_bias (updated
	//        within Iterate)
	// **********************************************
	
	ITERATION_COUNT[0] += dcount; // never overflows!
	ITERATION_COUNT[1] += dcount;
	
	if(ITERATION_COUNT[1] > 999999999)
	{
		ITERATION_COUNT[1] -= 1000000000;
		ITERATION_COUNT[2] ++;
	}
}

static void AppendCountStr(char *str)
{
	char tmp[31];
	
	if(ITERATION_COUNT[2])
	{
		int mask = 100000000; //10^8
		
		sprintf(tmp, "%i", ITERATION_COUNT[2]);
		strcat(str, tmp);
		
		while(mask > 1)
		{
			if(mask > ITERATION_COUNT[1])
			{
				strcat(str, "0");
			}
			mask /= 10;
		}
		sprintf(tmp, "%i", ITERATION_COUNT[1]);
		strcat(str, tmp);
	}
	else
	{
		sprintf(tmp, "%i", ITERATION_COUNT[1]);
		strcat(str, tmp);
	}
}

static void SPrintFloat(char *str, char *prefix, double t)
{
	char tmp[75];
	
	if(t < 0.0)
	{
		sprintf(tmp, "%1.16lf", t);
	}
	else
	{
		sprintf(tmp, " %1.16lf", t);
	}
	strcpy(str, prefix);
	strcat(str, tmp);
}

static void DrawStatus(int field)
{
	// ****************************************
	// Update icon/status bar. Use field=0 to
	// update everything, otherwise use field>0
	// to specify a particular field.
	// ****************************************
	
	zoom_t *zoom = &ZOOM_LIST[ZOOM_COUNT-1];
	char buf[200];
	
	glColor3dv(STAT_BAR_COLOR);
	
	SetPixelCoords(0, 0, ICON_BAR, WINDOW_Y, field == 0);
	SetPixelCoords(ICON_BAR, 0, WINDOW_X, STAT_BAR, field == 0);
	
	if(field == 0)
	{
		double asp = (double)VIEW_X / (double)VIEW_Y;
		
		glColor3dv(STAT_TEXT_COLOR);
		SPrintFloat(buf, "Min X= ", -asp/zoom->z - zoom->x);
		DrawText(10, 32+13*4, buf);
		SPrintFloat(buf, "Min Y= ", -1.0/zoom->z - zoom->y);
		DrawText(10, 32+13*3, buf);
		SPrintFloat(buf, "Max X= ", +asp/zoom->z - zoom->x);
		DrawText(10, 32+13*2, buf);
		SPrintFloat(buf, "Max Y= ", +1.0/zoom->z - zoom->y);
		DrawText(10, 32+13*1, buf);
		sprintf(buf, "Scale=  %.3lf", zoom->z);
		DrawText(10, 32+13*0, buf);
		
		SPrintFloat(buf, "Julia X= ", JULIA_X);
		DrawText(250, 32+13*2, buf);
		SPrintFloat(buf, "Julia Y= ", JULIA_Y);
		DrawText(250, 32+13*1, buf);
	}
	if(field == 0 || field == 1)
	{
		if(field == 1)
		{
			glColor3dv(STAT_BAR_COLOR);
			glRecti(250, 32+13*0-2, VIEW_X, 32+13*1-1);
		}
		glColor3dv(STAT_TEXT_COLOR);
		sprintf(buf, "Iteration Count= ");
		AppendCountStr(buf);
		DrawText(250, 32+13*0, buf);
	}
	if(field == 0 || field == 2)
	{
		int layer, iter = MOUSE_ITERATIONS & 0x7FFFFFFF;
		if(MOUSE_ITERATIONS == -1) layer = iter = -1;
		else if(MOUSE_ITERATIONS & 0x80000000) layer = 2; 
		else layer = 1;
		
		if(field == 2)
		{
			glColor3dv(STAT_BAR_COLOR);
			glRecti(250, 32+13*3-2, VIEW_X, 32+13*5-1);
		}
		glColor3dv(STAT_TEXT_COLOR);
		SPrintFloat(buf, "Mouse X= ", MOUSE_X);
		DrawText(250, 32+13*4, buf);
		SPrintFloat(buf, "Mouse Y= ", MOUSE_Y);
		DrawText(250, 32+13*3, buf);
		sprintf(buf, "ITER= %i", iter);
		DrawText(506, 32+13*4, buf);
		sprintf(buf, "LAYR= %i", layer);
		DrawText(506, 32+13*3, buf);
	}
	if(field == 0 || field == 3)
	{
		if(field == 3)
		{
			glColor3dv(STAT_BAR_COLOR);
			glRecti(100, 8-2, VIEW_X, 8+13-1);
		}
		if(QDisableRendering())
		{
			strcpy(buf, "Rendering: DONE");
		}
		else
		{
			if(!RENDERING_ENABLED)
			{
				strcpy(buf, "Rendering: PAUSED");
			}
			else if(SSAMP_SIZE > 1 && SLICE_RES == BLOCK_SIZE)
			{
				sprintf(buf,
				    "Rendering: %ix%i blocks, %ix%i supersampling...",
					SLICE_RES, SLICE_RES, SSAMP_SIZE, SSAMP_SIZE);
			}
			else
			{
				sprintf(buf, "Rendering: %ix%i blocks...",
					SLICE_RES, SLICE_RES);
			}
		}
		glColor3dv(STAT_TEXT_COLOR2);
		DrawText(100, 8, buf);
	}
	glColor3d(0.0, 0.0, 0.0);
	SetPixelCoords(ICON_BAR, STAT_BAR, WINDOW_X, WINDOW_Y, field == 0);
}

static pix_t ColorMin(pix_t p1, pix_t p2)
{
	pix_t r = _min(p1&255, p2&255); p1>>=8; p2>>=8;
	pix_t g = _min(p1&255, p2&255); p1>>=8; p2>>=8;
	pix_t b = _min(p1&255, p2&255);
	
	return r|(g<<8)|(b<<16);
}

static pix_t ColorMax(pix_t p1, pix_t p2)
{
	if(p2 == 0xFFFFFFFF) return p1;
	
	pix_t r = _max(p1&255, p2&255); p1>>=8; p2>>=8;
	pix_t g = _max(p1&255, p2&255); p1>>=8; p2>>=8;
	pix_t b = _max(p1&255, p2&255);
	
	return r|(g<<8)|(b<<16);
}

static pix_t ColorAdd(pix_t p1, pix_t p2)
{
	pix_t r = _min((p1&255)+(p2&255), 255); p1>>=8; p2>>=8;
	pix_t g = _min((p1&255)+(p2&255), 255); p1>>=8; p2>>=8;
	pix_t b = _min((p1&255)+(p2&255), 255);
	
	return r|(g<<8)|(b<<16);
}

static pix_t ColorSub(pix_t p1, pix_t p2)
{
	pix_t r = (pix_t)_max((int)((p1&255)-(p2&255)), 0); p1>>=8; p2>>=8;
	pix_t g = (pix_t)_max((int)((p1&255)-(p2&255)), 0); p1>>=8; p2>>=8;
	pix_t b = (pix_t)_max((int)((p1&255)-(p2&255)), 0);
	
	return r|(g<<8)|(b<<16);
}

static pix_t ColorMul(pix_t p1, pix_t p2)
{
	pix_t r = ((p1&255)*(p2&255))>>8; p1>>=8; p2>>=8;
	pix_t g = ((p1&255)*(p2&255))>>8; p1>>=8; p2>>=8;
	pix_t b = ((p1&255)*(p2&255))>>8;
	
	return r|(g<<8)|(b<<16);
}

static pix_t ColorAvg(pix_t p1, pix_t p2)
{
	p1 &= 0xFEFEFEFE;
	p2 &= 0xFEFEFEFE;
	return (p1+p2)>>1;
}

static pix_t Blend(pix_t *table, int index, double blend)
{
	// ***************************************
	// Performs bilinear interpolation between
	// color table entries.
	// ***************************************
	
	if(index == MAX_ITERATIONS) return table[index];
	
	pix_t rgb1 = table[index+0];
	pix_t rgb2 = table[index+1];
	double r1 = (double)(rgb1&255); rgb1>>=8;
	double g1 = (double)(rgb1&255); rgb1>>=8;
	double b1 = (double)(rgb1&255);
	double r2 = (double)(rgb2&255); rgb2>>=8;
	double g2 = (double)(rgb2&255); rgb2>>=8;
	double b2 = (double)(rgb2&255);
	
	pix_t r = (pix_t)(r1 + (r2-r1)*blend);
	pix_t g = (pix_t)(g1 + (g2-g1)*blend);
	pix_t b = (pix_t)(b1 + (b2-b1)*blend);
	if(r < 0) r = 0; else if(r > 255) r = 255;
	if(g < 0) g = 0; else if(g > 255) g = 255;
	if(b < 0) b = 0; else if(b > 255) b = 255;
	
	return r|(g<<8)|(b<<16);
}

static pix_t Iterate(double cx, double cy, int &iter)
{
	double x = cx, y = cy, dx = 0.0, dy = 0.0, s;
	pix_t auxc = 0xFFFFFFFF;
	int i, auxi = 0;
	
	if(JULIA_MODE == 1)
	{
		cx = JULIA_X;
		cy = JULIA_Y;
	}
	else if(JULIA_MODE == 2)
	{
		x = JULIA_X; // Julia/Mandelbrot hybrid fractal
		y = JULIA_Y;
	}
	for(iter = 0; iter <= ITERATIONS_PER_PIXEL; iter++)
	{
		s = x*x + y*y;
		
		if(s > 4.0 && ESCAPE1_COLOR_DEF) // escape 1
		{
			pix_t *table = ESCAPE1_COLOR_DAT.table;
			IncrementCount(iter);
			ITERATION_COUNT[3] = _min(ITERATION_COUNT[3], iter);
			
			if(ESCAPE2_COLOR_DEF >= 3 && auxc != 0xFFFFFFFF)
			{
				iter = auxi;
				return auxc;
			}
			i  = _max(0, iter-ZOOM_LIST[ZOOM_COUNT-1].iteration_bias);
			s -= 4.0;
			
			switch(ESCAPE1_COLOR_DEF)
			{
				case 1: return table[0];
				case 2: return table[i];
				case 3:
					s = 1.0 - s/ESCAPE1_BLEND_FACTOR;
					return Blend(table, i, _max(0.0, s));
				case 4:
					s *= ESCAPE1_XBAND_FACTOR;
					return table[_min((int)s, MAX_ITERATIONS)];
			}
		}
		if(iter >= ESCAPE2_MIN_ITER &&
		   iter <= ESCAPE2_MAX_ITER &&
		   ESCAPE2_COLOR_DEF)
		{
			double h;
			
			switch(ESCAPE2_EQUATION)
			{
				case 0: h = s; break; // bubbles;
				case 1: h = fabs(x*x - y*y); break; // spikes1
				case 2: h = fabs(dx*dx - dy*dy); break; // spikes2
				case 3: h = fabs(dx*x + dy*y); break; // rings
				case 4: h = fabs(dx*x - dy*y); break; // boomerangs
				case 5: h = fabs(dx*y - dy*x); break; // wires1
				case 6: h = fabs(x*y - dy*dx); break; // wires2
				case 7: h = fabs((x*x*x*x+y*y*y*y)/(x*x-y*y)); break;//clovers
				case 8: h = fabs(cos(dx)*y + sin(dy)*x); break; // spikes3
				case 9: h = fabs(fabs(x)*dx + fabs(y)*dy); break; // twists
			}
			if(h < ESCAPE2_THRESHOLD)
			{
				pix_t *table = ESCAPE2_COLOR_DAT.table;
				
				if(ESCAPE2_COLOR_DEF <= 2)
				{
					IncrementCount(iter);
				}
				h  = ESCAPE2_THRESHOLD - h;
				h *= ESCAPE2_SHADE_FACTOR;
				h /= ESCAPE2_THRESHOLD;
				
				switch(ESCAPE2_COLOR_DEF)
				{
					case 1:
						iter |= 0x80000000;
						return table[0];
					case 2:
						iter |= 0x80000000;
						return table[_min((int)h, MAX_ITERATIONS)];
					case 3:
						auxi = iter | 0x80000000;
						auxc = table[_min((int)h, MAX_ITERATIONS)];
						break;
					case 4:
						auxi = iter | 0x80000000;
						auxc = ColorMin(auxc, table[_min((int)h, MAX_ITERATIONS)]);
						break;
				}
			}
		}
		dx  = x;
		dy  = y;
		x   = cx + x*x - y*y;
		y   = cy + 2.0*dx*y;
		dx -= x;
		dy -= y;
		
		if(dx*dx + dy*dy < 1e-14 && ESCAPE2_COLOR_DEF <= 2)
		{
			iter = -1;
			break; // optimization for initial rendering
		}
	}
	IncrementCount(MAX_ITERATIONS);
	
	if(auxc != 0xFFFFFFFF     &&
	   ESCAPE1_COLOR_DEF == 0 &&
	   ESCAPE2_COLOR_DEF >= 3 )
	{
		// ***************************************
		// If we don't make this distinction, then
		// we'll get a pure black screen.
		// ***************************************
		
		iter = auxi;
		return auxc;
	}
	if(HYSTERIA_MODE)
	{
		// *************************************
		// Hysteria mode (c) James Beattie, 1999
		// *************************************		
		
		s *= HYSTERIA_FACTOR;
		
		pix_t hg = (pix_t)(255.0*_max(cos(s + 0.000000000000000), 0.0));
		pix_t hb = (pix_t)(255.0*_max(cos(s + 3.141592653589793), 0.0));
		
		iter = -1;
		return (hg<<8)|(hb<<16);
	}
	iter = -1;
	return 0;
}

static pix_t RenderPixel(int i, int j)
{
	// *********************************************
	// Compute pixel color for screen position (i,j)
	// *********************************************
	
	int iter;
	double cx = (double)(i*SLICE_RES);
	double cy = (double)(j*SLICE_RES);
	Pix2WorldCoords(cx, cy);
	
	if(SLICE_RES > BLOCK_SIZE)
	{
		return Iterate(cx, cy, iter);
	}
	else
	{
		double r_sum = 0.5; // offset for round-to-nearest quantization
		double g_sum = 0.5;
		double b_sum = 0.5;
		
		double d  = 2.0/(ZOOM_LIST[ZOOM_COUNT-1].z*(double)VIEW_Y);
		double dd = FOCUS*d/(double)SSAMP_SIZE;
		double o  = dd*(1.0-(double)SSAMP_SIZE)*0.5;
		double s  = 1.0/(double)(SSAMP_SIZE*SSAMP_SIZE);
		double x1 = cx + o;
		
		cy += o; // offset
		
		for(int k = 0, v = 0; v < SSAMP_SIZE; v++)
		{
			cx = x1; // initial worldspace x
			
			for(int u = 0; u < SSAMP_SIZE; u++, k++)
			{
				pix_t rgb = Iterate(cx, cy, iter);
				
				r_sum += s*(double)(rgb&255); rgb>>=8;
				g_sum += s*(double)(rgb&255); rgb>>=8;
				b_sum += s*(double)(rgb&255);
				
				cx += dd;
			}
			cy += dd;
		}
		pix_t r = (pix_t)r_sum;
		pix_t g = (pix_t)g_sum;
		pix_t b = (pix_t)b_sum;
		
		// ***************************
		// Clamp values to valid range
		// ***************************
		
		if(r < 0) r = 0; else if(r > 255) r = 255;
		if(g < 0) g = 0; else if(g > 255) g = 255;
		if(b < 0) b = 0; else if(b > 255) b = 255;
		
		return r|(g<<8)|(b<<16);
	}
	return 0;
}

static void RedrawXLine(int x, int y, int width)
{
	pix_t tmpbuf[MAX_WINDOW_X], *buf;
	int res, shr;
	
	// ***********************
	// Clip to window boundary
	// ***********************
	
	if(x > VIEW_X-1 || x < 1-width) return;
	if(y > VIEW_Y-1 || y < 0)       return;
	
	if(x < 0) width += x, x = 0;
	if(width > VIEW_X-x) width = VIEW_X-x;
	
	// **********************************************
	// Determine slice resolution based on y-position
	// **********************************************
	
	if(y < SLICE_HLT_POS) res = SLICE_RES;
	else res = SLICE_RES*2;
	
	// *****************************
	// Read pixels from slice buffer
	// *****************************
	
	buf = SLICE_BUF + (((y&~(res-1))+1)*2 + res)*(VIEW_X/2) - VIEW_X/res;
	shr = LOG2TABLE[res];
	
	for(int i = 0; i < width; i++)
	{
		tmpbuf[i] = buf[(x+i)>>shr];
	}
	// *************
	// Render pixels
	// *************
	
	glRasterPos2i(x, y);
	glDrawPixels(width, 1, GL_RGBA, GL_UNSIGNED_BYTE, tmpbuf);
}

static void RedrawYLine(int x, int y, int height)
{
	pix_t tmpbuf[MAX_WINDOW_Y], *buf;
	int res;
	
	// ***********************
	// Clip to window boundary
	// ***********************
	
	if(y > VIEW_Y-1 || y < 1-height) return;
	if(x > VIEW_X-1 || x < 0)        return;
	
	if(y < 0) height += y, y = 0;
	if(height > VIEW_Y - y) height = VIEW_Y - y;
	
	// *****************************
	// Read pixels from slice buffer
	// *****************************
	
	res = SLICE_RES;
	buf = SLICE_BUF + (res + 2)*(VIEW_X/2) + x/res - VIEW_X/res;
	
	for(int i = 0; i < height; i++)
	{
		if(y+i >= SLICE_HLT_POS && res == SLICE_RES)
		{
			res = SLICE_RES*2;
			buf = SLICE_BUF + (res + 2)*(VIEW_X/2) + x/res - VIEW_X/res;
		}
		tmpbuf[i] = buf[((y+i)&~(res-1))*VIEW_X];
	}
	// *************
	// Render pixels
	// *************
	
	glRasterPos2i(x, y);
	glDrawPixels(1, height, GL_RGBA, GL_UNSIGNED_BYTE, tmpbuf);
}

static void Redraw(void)
{
	// ************************************************
	// Redraw entire window, including status and view.
	// NOTE: Zoom box is also reset by this function.
	// ************************************************
	
	BOX_STATE  = 0;
	BOX_HILITE = 0;
	BOX_UPDATE = 0;
	
	memset(BOX, 0, sizeof(box_t)*2);
	
	DrawStatus(0);
	
	for(int y = 0; y < VIEW_X; y++)
	{
		RedrawXLine(0, y, VIEW_X);
	}
	DrawSliceHilite();
	
	glFlush();
	glutSwapBuffers();
}

static void EraseBox(box_t *box)
{
	// *****************************************
	// Erase old zoom box by copying pixels from
	// slice buffer info frame buffer.
	// *****************************************
	
	if(box->x1 == box->x2 && box->y1 == box->y2)
	{
		return;
	}
	int x1 = _min(box->x1, box->x2);
	int y1 = _min(box->y1, box->y2);
	int x2 = _max(box->x1, box->x2);
	int y2 = _max(box->y1, box->y2);
	
	RedrawXLine(x1,   y1,   x2 - x1);
	RedrawXLine(x1,   y2-1, x2 - x1);
	RedrawYLine(x1,   y1+1, y2 - y1-2);
	RedrawYLine(x2-1, y1+1, y2 - y1-2);
}

static void DrawBox(box_t *box)
{
	// *************
	// Draw zoom box
	// *************
	
	if(box->x1 == box->x2 && box->y1 == box->y2)
	{
		return;
	}
	int x1 = _min(box->x1, box->x2);
	int y1 = _min(box->y1, box->y2);
	int x2 = _max(box->x1, box->x2);
	int y2 = _max(box->y1, box->y2);
	
	if(BOX_HILITE) glColor3dv(BOX_HILITE_COLOR);
	else           glColor3dv(BOX_NO_HLT_COLOR);
	
	glEnable(GL_BLEND);
	glRecti(x1,   y1,   x2,   y1+1);
	glRecti(x1,   y2-1, x2,   y2);
	glRecti(x1,   y1+1, x1+1, y2-1);
	glRecti(x2-1, y1+1, x2,   y2-1);
	glDisable(GL_BLEND);
}

static int DrawSlice(void)
{
	// ***********************************************
	// Function returns 1 iff slice was actually drawn
	// ***********************************************
	
	static pix_t tmpbuf[MAX_WINDOW_X];
	int x, y = SLICE_POS;
	int no_skip = (y & 1) ||
	              (SLICE_RES == BLOCK_SIZE && SSAMP_SIZE > 1) ||
	              (SLICE_RES == MAX_SLICE_RES);
	
	// **********************************************************
	// Render pixels into SLICE_TMP_BUF; if ITERATIONS_PER_UPDATE
	// limit is reached, then draw pixels into framebuffer, else
	// return and wait for next pass.
	// **********************************************************
	
	ITERATION_COUNT[0] = 0;
	
	if(no_skip)
	{
		for(x = SLICE_TMP_COUNT; x < SLICE_X; x++)
		{
			if(ITERATION_COUNT[0] > ITERATIONS_PER_UPDATE) break;
			tmpbuf[x] = RenderPixel(x, y);
		}
	}
	else
	{
		for(x = SLICE_TMP_COUNT; x < SLICE_X; x+=2)
		{
			if(ITERATION_COUNT[0] > ITERATIONS_PER_UPDATE) break;
			tmpbuf[x>>1] = RenderPixel(x+1, y);
		}
	}
	if(x == SLICE_X)
	{
		SLICE_TMP_COUNT = 0;
	}
	else
	{
		SLICE_TMP_COUNT = x;
		return 0; // not done yet
	}
	// **********************************************************
	// ITERATIONS_PER_UPDATE limit has been reached; continue and
	// draw pixels into framebuffer.
	// **********************************************************
	
	glPixelZoom((float)SLICE_RES, (float)SLICE_RES);
	
	// ************************************
	// First, copy pixels into slice buffer
	// ************************************
	
	pix_t *buf = SLICE_BUF + (SLICE_RES +
	(y*SLICE_RES+1)*2)*(VIEW_X/2) - SLICE_X;
	
	if(no_skip)
	{
		memcpy(buf, tmpbuf, sizeof(pix_t)*SLICE_X);
	}
	else
	{
		pix_t *buf2 = buf + (SLICE_X + SLICE_RES*VIEW_X)/2;
		
		for(x = 0; x < SLICE_X; x+=2)
		{
			buf[x]   = buf2  [x>>1];
			buf[x+1] = tmpbuf[x>>1];
		}
	}
	// **********
	// Draw slice
	// **********
	
	glRasterPos2i(0, y*SLICE_RES);
	glDrawPixels(SLICE_X, 1, GL_RGBA, GL_UNSIGNED_BYTE, buf);
	glPixelZoom(1.f, 1.f);
	
	SLICE_HLT_POS = (y+1)*SLICE_RES;
	
	return 1;
}

static void DrawSliceHilite(void)
{
	// *********************
	// Draw hilited scanline
	// *********************
	
	glColor3dv(SLICE_HLT_COLOR);
	glRecti(0, SLICE_HLT_POS, VIEW_X, SLICE_HLT_POS+1);
}

static void Zoom(box_t *box)
{
	zoom_t Z = ZOOM_LIST[ZOOM_COUNT-1];
	
	// *********************************
	// Convert to normalized coordinates
	// *********************************
	
	double x1 = +(2.0*(double)box->x1/(double)VIEW_X - 1.0);
	double y1 = +(2.0*(double)box->y1/(double)VIEW_Y - 1.0);
	double x2 = +(2.0*(double)box->x2/(double)VIEW_X - 1.0);
	double y2 = +(2.0*(double)box->y2/(double)VIEW_Y - 1.0);
	x1 *= (double)VIEW_X/(double)VIEW_Y;
	x2 *= (double)VIEW_X/(double)VIEW_Y;
	if(x1 > x2) {double x3 = x1; x1 = x2; x2 = x3;}
	if(y1 > y2) {double y3 = y1; y1 = y2; y2 = y3;}
	
	// *********************
	// Create zoom transform
	// *********************
	
	double z2;
	
	if((double)VIEW_X*(y2-y1) < (double)VIEW_Y*(x2-x1))
		z2 = 2.0*((double)VIEW_X/(double)VIEW_Y)/(x2-x1); else
		z2 = 2.0/(y2-y1);
	
	// ***************************
	// Concatenate zoom transforms
	// ***************************
	
	ZOOM_LIST[ZOOM_COUNT].x = -0.5*(x1+x2)/Z.z + Z.x;
	ZOOM_LIST[ZOOM_COUNT].y = -0.5*(y1+y2)/Z.z + Z.y;
	ZOOM_LIST[ZOOM_COUNT].z = z2*Z.z;
	ZOOM_LIST[ZOOM_COUNT].iteration_bias = Z.iteration_bias;
	ZOOM_COUNT++;
	Clear();
}

static void GLUT__Display(void)
{
	// ********************************************
	// First, see if slice -really- needs rendering
	// ********************************************
	
	if(SLICE_UPDATE) SLICE_UPDATE = DrawSlice();
	
	// ********************************
	// Update zoom box and slice hilite
	// ********************************
	
	if(BOX_UPDATE || SLICE_UPDATE)
	{
		// ************************************
		// These objects are rendered together,
		// since they may be overlapping..
		// ************************************
		
		EraseBox(&BOX[1]);
		DrawBox(&BOX[0]);
		BOX[1] = BOX[0];
		DrawSliceHilite();
	}
	if(REALTIME_STAT_UPDATE)
	{
		if(SLICE_UPDATE) DrawStatus(1); // update iteration count
		if(MOUSE_UPDATE) DrawStatus(2); // update mouse position
	}
	// *****************************
	// Prepare to process next slice
	// *****************************
	
	if(SLICE_UPDATE)
	{
		if(++SLICE_POS*SLICE_RES >= VIEW_Y)
		{
			if(SLICE_RES == BLOCK_SIZE)
			{
				RENDERING_ENABLED = 0;
			}
			else
			{
				SLICE_POS  = SLICE_HLT_POS = 0;
				SLICE_RES /= 2;
				
				SLICE_X = VIEW_X/SLICE_RES;
				SLICE_Y = VIEW_Y/SLICE_RES;
				
				if(SLICE_RES == 1 && SSAMP_SIZE > 1)
				{
					// ************************************
					// Reset iteration count on final pass,
					// since we aren't recycling old pixels
					// ************************************
					
					ITERATION_COUNT[0] = 0;
					ITERATION_COUNT[1] = 0;
					ITERATION_COUNT[2] = 0;
				}
			}
			DrawStatus(1); // update iteration count
			DrawStatus(3); // update rendering state
		}
	}
	if(SLICE_UPDATE || BOX_UPDATE || MOUSE_UPDATE)
	{
		glFlush();
		glutSwapBuffers();
	}
	BOX_UPDATE   = 0;
	SLICE_UPDATE = 0;
	MOUSE_UPDATE = 0;
}

static void GLUT__Reshape(int width, int height)
{
	// **************************************************
	// Adjust window size to be multiple of MAX_SLICE_RES
	// **************************************************
	
	VIEW_X = width  - ICON_BAR;
	VIEW_Y = height - STAT_BAR;
	if(VIEW_X > MAX_WINDOW_X) VIEW_X = MAX_WINDOW_X;
	if(VIEW_Y > MAX_WINDOW_Y) VIEW_Y = MAX_WINDOW_Y;
	if(VIEW_X < MIN_WINDOW_X) VIEW_X = MIN_WINDOW_X;
	if(VIEW_Y < MIN_WINDOW_Y) VIEW_Y = MIN_WINDOW_Y;
	VIEW_X &= ~(MAX_SLICE_RES-1);
	VIEW_Y &= ~(MAX_SLICE_RES-1);
	WINDOW_X = VIEW_X + ICON_BAR;
	WINDOW_Y = VIEW_Y + STAT_BAR;
	
	if(width != WINDOW_X || height != WINDOW_Y)
	{
		glutReshapeWindow(WINDOW_X, WINDOW_Y);
	}
	// ************************
	// Set up pixel-coordinates
	// ************************
	
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glOrtho(0.f, (float)VIEW_X, 0.f, (float)VIEW_Y, 1.f, -1.f);
	glViewport(ICON_BAR, STAT_BAR, VIEW_X, VIEW_Y);
	
	if(APP_RESHAPE_EVENT)
	{
		Redraw(); // reshape occured via load-rendering-state
	}
	else
	{
		Clear(); // reshape occured via GLUT interface
	}
	APP_RESHAPE_EVENT = 0;
}

static void GLUT__Keyboard(unsigned char key, int x, int y)
{
	if(key == 27)  exit(0); // ESC key quits program
	if(key == 'c') Clear();
	if(key == 'd') Redraw();
	if(key == 'p')
	{
		RENDERING_ENABLED = 0;
		DrawStatus(3);  // update rendering state
		BOX_UPDATE = 1; // this forces a status update
	}
	if(key == 'r')
	{
		RENDERING_ENABLED = 1;
		DrawStatus(3);  // update rendering state
		BOX_UPDATE = 1; // this forces a status update
	}
	if(key == 8 && ZOOM_COUNT > 1) // BACKSPACE key restores prev. zoom state
	{
		ZOOM_COUNT--;
		
		if(JULIA_MODE && ZOOM_COUNT <= JULIA_ZOOM_INDEX)
		{
			JULIA_MODE = 0;
			glutSetMenu(M_ID[0]);
			glutChangeToSubMenu(1,
			"Mode: Mandelbrot Set", M_ID[1]);
		}
		Clear();
	}
	if(key == 'b')
	{
		ZOOM_LIST[ZOOM_COUNT-1].iteration_bias = ITERATION_COUNT[3];
		
		if(ESCAPE1_COLOR_DEF == 2 || ESCAPE1_COLOR_DEF == 3)
		{
			Clear();
		}
	}
	if((key == 'j' || key == 'h') && !JULIA_MODE)
	{
		y = VIEW_Y - y, x = x - ICON_BAR;
		
		// ****************************************
		// Compute world-coordinates of mouse point
		// ****************************************
		
		JULIA_X = (double)x;
		JULIA_Y = (double)y;
		Pix2WorldCoords(JULIA_X, JULIA_Y);
		
		if(key == 'j')
		{
			glutSetMenu(M_ID[0]);
			glutChangeToSubMenu(1, "Mode: Julia Set", M_ID[1]);
			JULIA_MODE = 1;
		}
		else
		{
			glutSetMenu(M_ID[0]);
			glutChangeToSubMenu(1,
			"Mode: Julia/Mandelbrot Hybrid Fractal", M_ID[1]);
			JULIA_MODE = 2;
		}
		JULIA_ZOOM_INDEX = ZOOM_COUNT;
		ZOOM_LIST[ZOOM_COUNT].x = 0.00;
		ZOOM_LIST[ZOOM_COUNT].y = 0.00;
		ZOOM_LIST[ZOOM_COUNT].z = 0.75; // default
		ZOOM_LIST[ZOOM_COUNT].iteration_bias = 0;
		ZOOM_COUNT++;
		Clear();
	}
	if(key == 'm' && JULIA_MODE)
	{
		JULIA_MODE = 0;
		ZOOM_COUNT = JULIA_ZOOM_INDEX;
		glutSetMenu(M_ID[0]);
		glutChangeToSubMenu(1,
		"Mode: Mandelbrot Set", M_ID[1]);
		Clear();
	}
}

static void GLUT__Mouse(int button, int state, int x, int y)
{
	int isdown = (state == GLUT_DOWN) ? 1 : 0;
	
	if(button != GLUT_LEFT_BUTTON) return; // not interesting
	if(isdown == (BOX_STATE & 1))  return; // not interesting
	
	y = VIEW_Y - y, x = x - ICON_BAR;
	
	switch(BOX_STATE)
	{
		case 0: // begin drag
		{
			BOX_HILITE = 0;
			BOX_STATE++;
			break;
		}
		case 1: // end drag
		{
			if(BOX[0].x1 != BOX[0].x2 || BOX[0].y1 != BOX[0].y2)
			{
				BOX_STATE++;
			}
			else
			{
				BOX_STATE--;
			}
			break;
		}
		case 2: // click in box
		{
			if(QPointInBox(&BOX[0], x, y))
			{
				BOX_HILITE = 1;
				BOX_STATE++;
			}
			else
			{
				BOX_HILITE = 0;
				BOX_STATE--;
			}
			break;
		}
		case 3: // release
		{
			if(QPointInBox(&BOX[0], x, y))
			{
				BOX_HILITE = 0;
				BOX_STATE  = 0;
				Zoom(&BOX[0]);
			}
			else
			{
				BOX_HILITE = 0;
				BOX_STATE  = 2;
			}
		}
	}
	if(BOX_STATE == 1)
	{
		if(x < 0) x = 0; else if(x > VIEW_X) x = VIEW_X;
		if(y < 0) y = 0; else if(y > VIEW_Y) y = VIEW_Y;
		
		BOX[0].x1 = BOX[0].x2 = x;
		BOX[0].y1 = BOX[0].y2 = y;
	}
	BOX_UPDATE = 1;
}

static void GLUT__Motion(int x, int y)
{
	y = VIEW_Y - y, x = x - ICON_BAR;
	
	// ************************
	// Handle zoom box dragging
	// ************************
	
	switch(BOX_STATE)
	{
		case 1:
		{
			if(x < 0) x = 0; else if(x > VIEW_X) x = VIEW_X;
			if(y < 0) y = 0; else if(y > VIEW_Y) y = VIEW_Y;
			
			BOX[0].x2 = x;
			BOX[0].y2 = y;
			if(BOX[0].x2 == BOX[0].x1) BOX[0].x2++;
			if(BOX[0].y2 == BOX[0].y1) BOX[0].y2++;
			BOX_UPDATE = 1;
			break;
		}
		case 3:
		{
			int hilite = QPointInBox(&BOX[0], x, y);
			
			if(BOX_HILITE != hilite)
			{
				BOX_HILITE = hilite;
				BOX_UPDATE = 1;
			}
		}
	}
	// ************************************************
	// Convert mouse-coords to world-coords and display
	// ************************************************
	
	if(REALTIME_STAT_UPDATE)
	{
		double mx = (double)x;
		double my = (double)y;
		Pix2WorldCoords(mx, my);
		MOUSE_X = mx;
		MOUSE_Y = my;
		MOUSE_UPDATE = 1;
		(void)Iterate(mx, my, MOUSE_ITERATIONS);
	}
}

static void GLUT__PassiveMotion(int x, int y)
{
	if(!REALTIME_STAT_UPDATE) return;
	
	// ************************************************
	// Convert mouse-coords to world-coords and display
	// ************************************************
	
	double mx = (double)(x - ICON_BAR);
	double my = (double)(VIEW_Y - y);
	Pix2WorldCoords(mx, my);
	MOUSE_X = mx;
	MOUSE_Y = my;
	MOUSE_UPDATE = 1;
	(void)Iterate(mx, my, MOUSE_ITERATIONS);
}

static void GLUT__Idle(void)
{
	if(RENDERING_ENABLED)
	{
		if(QDisableRendering())
		{
			RENDERING_ENABLED = 0; // oops!
		}
		else
		{
			SLICE_UPDATE = 1;
		}
	}
	glutPostRedisplay();
}

int main(int argc, char *argv[])
{
	VIEW_X = 640, WINDOW_X = VIEW_X + ICON_BAR;
	VIEW_Y = 480, WINDOW_Y = VIEW_Y + STAT_BAR;
	
	fprintf(stdout, "Mandel: Fractal Explorer\n");
	fprintf(stdout, "(c) Bernie Freidin, 1999-2000\n\n");
	fprintf(stdout, "Please refer to 'Mandel Paper.doc' for instructions on use\n");
	
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE);
	glutInitWindowSize(WINDOW_X, WINDOW_Y);
	int win_id = glutCreateWindow(argv[0]);
	glutDisplayFunc(GLUT__Display);
	glutReshapeFunc(GLUT__Reshape);
	glutKeyboardFunc(GLUT__Keyboard);
	glutMouseFunc(GLUT__Mouse);
	glutMotionFunc(GLUT__Motion);
	glutPassiveMotionFunc(GLUT__PassiveMotion);
	glutIdleFunc(GLUT__Idle);
	glutSetCursor(GLUT_CURSOR_CROSSHAIR);
	glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
	glBlendFunc(GL_ONE, GL_ONE); // pure additive
	
	Initialize();
	glutMainLoop();
	return 0;
}

This page © Bernie Freidin, 2000.