// cs5610p3.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"

#include "texture.h"
#include "glew.h"
#include <GL/glut.h>
#include "GL/glui.h"
#include "GL/glu.h"
#include <math.h>
#include <assert.h>
#include <time.h>
#include <iostream>
using namespace std;

int	main_window;

// the camera info
int screenx, screeny;
float eye[3];
float lookat[3];
float up[3];

//light
float lightxz[2];
float lighty;
float light_position[]={0.0,0.0,0.0,1.0};

// pointers for all of the glui controls
GLUI *glui;
GLUI_Rollout		*object_rollout;
GLUI_RadioGroup		*object_type_radio;
GLUI_Listbox			*object_choice;
GLUI_Rotation		*object_rotation;
float live_object_rotation[16];
GLUI_Translation	*object_xz_trans;
float live_object_xz_trans[2];
GLUI_Translation	*object_y_trans;
float live_object_y_trans;
GLUI_Checkbox *animate;
int live_animate=1;
GLUI_Spinner *speed_spinner;
GLUI_Spinner *num_spinner;
GLUI_Spinner *windx_spinner;
GLUI_Spinner *windy_spinner;
GLUI_Spinner *windz_spinner;
//float live_speed=0.1;
GLUI_Checkbox *wireframe_check;
int live_wireframe=0;

// the user id's that we can use to identify which control
// caused the callback to be called
#define PI 3.1415926f
#define CB_DEPTH_BUFFER 0
#define CB_EM 1
#define CB_OBJ_CHOICE 2
#define CB_ROT 3
#define CB_XZ 4
#define CB_Y 5
#define RNDBUT 6
#define RRLCHECK 7
#define WRIGGLES_CB 8
#define WINDX_CB 9
#define WINDY_CB 10
#define WINDZ_CB 11

#define LIGHTX 0
#define LIGHTY 0
#define LIGHTZ 0
#define LIGHTW 1
#define LAX 0
#define LAY 4
#define LAZ 0
#define EPX 0
#define EPY 4
#define EPZ 6
#define UPX 0
#define UPY 1
#define UPZ 0
#define OFS 0.04
#ifndef CWD
#define CWD ""
#endif

//textures
#define NUMTEX 9
struct rgbimgt{
	unsigned * ptr;
	int xdim, ydim, cdim;
};
static GLuint textures[NUMTEX];
rgbimgt texref[NUMTEX];

//animation timing variables
long linear=0, timer1=0, timer2=0;
float speedfactor=0.3;
#define AVGN 20
int scaled[AVGN];
int cscaled=0;
int scaledpos=0;
int scaledtot=10;
float scaledavg=1;
float loopaccum=1;
int loop=0;

//debug var
int offendertime=0;

//particles
#define DIE 0
#define NORM 1
#define BORN 2
#define FIRE 0
#define SPARK 1
#define SMOKE 2
//#define FIREDENSITY 200
int FIREDENSITY=2;
#define SPARKMAX 2000
#define SMOKEMAX 1000
int numfire=0;  //i.e. the rockets
int numspark=0;  //explosions
int numsmoke=0;  //smoke
struct particle{
	float oldpos[3];
	float newpos[3];
	float vel[3];
	float col[4];
	int age;
	void (*agefunc)(particle*);
	int maxage;
	void (*deathfunc)(particle*);
	particle * hnext;
	particle * hprev;
	char type;
	char gen;
};


//hash buckets
#define DELTA 0.000001
#define XBUCKETMIN -15.0f
#define XBUCKETMAX 15.0f
#define YBUCKETMIN 0.0f
#define YBUCKETMAX 30.0f
#define ZBUCKETMIN -15.0f
#define ZBUCKETMAX 15.0f
#define XBUCKETS 10
#define YBUCKETS 10
#define ZBUCKETS 10
float XBUCKETRANGE =(XBUCKETMAX-XBUCKETMIN);
float YBUCKETRANGE =(YBUCKETMAX-YBUCKETMIN);
float ZBUCKETRANGE =(ZBUCKETMAX-ZBUCKETMIN);
float XBUCKETSIZE =(XBUCKETRANGE/XBUCKETS);
float YBUCKETSIZE =(YBUCKETRANGE/YBUCKETS);
float ZBUCKETSIZE =(ZBUCKETRANGE/ZBUCKETS);
//#define XBUCKETRANGE (XBUCKETMAX-XBUCKETMIN)
//#define YBUCKETRANGE (YBUCKETMAX-YBUCKETMIN)
//#define ZBUCKETRANGE (ZBUCKETMAX-ZBUCKETMIN)
//#define XBUCKETSIZE (XBUCKETRANGE/XBUCKETS)
//#define YBUCKETSIZE (YBUCKETRANGE/YBUCKETS)
//#define ZBUCKETSIZE (ZBUCKETRANGE/ZBUCKETS)
struct parbkt{
	int num;
	float avgcol[2][2][2][7];  //[cx][cy][cz][rgbaxyz]
	particle * list;
};

parbkt parhash[XBUCKETS][YBUCKETS][ZBUCKETS];  //divide the world up into buckets xyz

//physics
float gravity=0.00005f;
float wind[3];
float slowdown=0.9f;




//
//BEGIN CODE  *************************************************************
//



//hash funcs
void InitHash(){
	int x, y, z, x1, y1, z1;
	for (x=0; x<XBUCKETS; x++)
		for (y=0; y<YBUCKETS; y++)
			for (z=0; z<ZBUCKETS;z++){
				for (x1=0; x1<2; x1++)
				for (y1=0; y1<2; y1++)
				for (z1=0; z1<2; z1++){
					parhash[x][y][z].avgcol[x1][y1][z1][0]=0;  //rgb
					parhash[x][y][z].avgcol[x1][y1][z1][1]=0;
					parhash[x][y][z].avgcol[x1][y1][z1][2]=0;
					parhash[x][y][z].avgcol[x1][y1][z1][4]=XBUCKETMIN+(x+x1)*XBUCKETSIZE+XBUCKETSIZE/2.0f;  //xyz
					parhash[x][y][z].avgcol[x1][y1][z1][5]=YBUCKETMIN+(y+y1)*YBUCKETSIZE+YBUCKETSIZE/2.0f;
					parhash[x][y][z].avgcol[x1][y1][z1][6]=ZBUCKETMIN+(z+z1)*ZBUCKETSIZE+ZBUCKETSIZE/2.0f;
				}
				parhash[x][y][z].list=NULL;
				parhash[x][y][z].num=0;
			}
	wind[0]=0;
	wind[1]=0;
	wind[2]=0;
	for(x=0; x<AVGN; x++) scaled[x]=1;

}

int onlist(particle *head, particle *which){
	particle *end, *tracer;
	if(!head || !which) return 0;

	end=head->hprev; tracer=head;
	while(end!=tracer){
		if (tracer==which) return 1;
		tracer=tracer->hnext;
	}
	if(tracer==which) return 1;
	return 0;
}

int cntlist(particle *head){
	particle *end, *tracer;
	int cnt=0;

	if(!head) return 0;

	end=head->hprev; tracer=head;
	while(end!=tracer){
		cnt++;
		tracer=tracer->hnext;
	}
	cnt++;

	return cnt;
}

inline parbkt * hashpnt(float pos[3]){
	int x, y, z;
	x=( pos[0] - XBUCKETMIN ) * XBUCKETS / XBUCKETRANGE;
	y=( pos[1] - YBUCKETMIN )* YBUCKETS / YBUCKETRANGE;	
	z=( pos[2] - ZBUCKETMIN )* ZBUCKETS / ZBUCKETRANGE;
	assert(x>=0 && x < XBUCKETS);
	assert(y>=0 && y < YBUCKETS);
	assert(z>=0 && z < ZBUCKETS);
	return & parhash[x]
					[y]
					[z];
}

#define sqr(x) (x)*(x)

void HashPart(particle * which, int mode){
	parbkt * oldhash, *newhash;
	float  r;
	int x, y, z;

	oldhash = hashpnt(which->oldpos);
	newhash = hashpnt(which->newpos);

	//first, clean up any old hashes this was on
	if(mode!=BORN){
		//remove the color contributions
		for(x=0; x<2; x++)
		for(y=0; y<2; y++)
		for(z=0; z<2; z++){
			r=sqr(oldhash->avgcol[x][y][z][4]-which->oldpos[0])+
				sqr(oldhash->avgcol[x][y][z][5]-which->oldpos[1])+
				sqr(oldhash->avgcol[x][y][z][6]-which->oldpos[2]);
			oldhash->avgcol[x][y][z][0]-=which->col[0]/r;
			oldhash->avgcol[x][y][z][1]-=which->col[1]/r;
			oldhash->avgcol[x][y][z][2]-=which->col[2]/r;
		}

		//remove this from the hash
		if(which->hnext==which) oldhash->list=NULL;  //alone on this hash
		else{  //not alone on this hash
			if(oldhash->list == which) oldhash->list = which->hnext;
			which->hnext->hprev=which->hprev;
			which->hprev->hnext=which->hnext;
		}
		oldhash->num--;
	}

	if(mode==DIE) return;

	//put in the new hash
	if(!newhash->list) {  //list is empty
		which->hnext=which;
		which->hprev=which;
		newhash->list=which;
	} else {  //list is not empty
		which->hnext=newhash->list;
		which->hprev=newhash->list->hprev;
		newhash->list->hprev->hnext=which;
		newhash->list->hprev=which;
	}
	newhash->num++;
	//add the color contributions
	for(x=0; x<2; x++)
	for(y=0; y<2; y++)
	for(z=0; z<2; z++){
			r=sqr(newhash->avgcol[x][y][z][4]-which->newpos[0])+
				sqr(newhash->avgcol[x][y][z][5]-which->newpos[1])+
				sqr(newhash->avgcol[x][y][z][6]-which->newpos[2]);
			newhash->avgcol[x][y][z][0]+=which->col[0]/r;
			newhash->avgcol[x][y][z][1]+=which->col[1]/r;
			newhash->avgcol[x][y][z][2]+=which->col[2]/r;
	}

}


//particle death funcs
void Nothing(particle *which){}
void BasicShower(particle *which){
	particle * newpart;
	
	//create smoke
	for (int ctr=0; ctr<10; ctr++){
		numsmoke++;
		newpart=(particle *)malloc(sizeof(particle));
		newpart->newpos[0]=which->newpos[0]+1.*(float)rand()/RAND_MAX-0.5;
		newpart->newpos[1]=which->newpos[1]+1.*(float)rand()/RAND_MAX-0.5;
		newpart->newpos[2]=which->newpos[2]+1.*(float)rand()/RAND_MAX-0.5;
		if(newpart->newpos[0]>=XBUCKETMAX) newpart->newpos[0]=XBUCKETMAX-DELTA;
		if(newpart->newpos[0]<=XBUCKETMIN) newpart->newpos[0]=XBUCKETMIN+DELTA;
		if(newpart->newpos[1]>=YBUCKETMAX) newpart->newpos[1]=YBUCKETMAX-DELTA;
		if(newpart->newpos[1]<=YBUCKETMIN) newpart->newpos[1]=YBUCKETMIN+DELTA;
		if(newpart->newpos[2]>=ZBUCKETMAX) newpart->newpos[2]=ZBUCKETMAX-DELTA;
		if(newpart->newpos[2]<=ZBUCKETMIN) newpart->newpos[2]=ZBUCKETMIN+DELTA;
		newpart->oldpos[0]=0;
		newpart->oldpos[1]=0;
		newpart->oldpos[2]=0;
		newpart->vel[0]=which->vel[0]/20+0.01*(float)rand()/RAND_MAX-0.005;
		newpart->vel[1]=which->vel[1]/20+0.01*(float)rand()/RAND_MAX-0.005;
		newpart->vel[2]=which->vel[2]/20+0.01*(float)rand()/RAND_MAX-0.005;
		newpart->col[0]=0.2;
		newpart->col[1]=0.2;
		newpart->col[2]=0.2;
		newpart->age=0;
		newpart->maxage=5000;
		newpart->type=SMOKE;

		newpart->agefunc=Nothing;
		newpart->gen=which->gen+1;
		newpart->deathfunc=Nothing;
		newpart->hnext=NULL;
		newpart->hprev=NULL;
		HashPart(newpart, BORN);
		if(numsmoke>SMOKEMAX) break;
	}

	//create new particles
	for (int ctr=0; ctr<10; ctr++){
		numspark++;
		newpart=(particle *)malloc(sizeof(particle));
		newpart->newpos[0]=which->newpos[0];
		newpart->newpos[1]=which->newpos[1];
		newpart->newpos[2]=which->newpos[2];
		newpart->oldpos[0]=0;
		newpart->oldpos[1]=0;
		newpart->oldpos[2]=0;
		newpart->vel[0]=which->vel[0]+0.03*(float)rand()/RAND_MAX-0.015;
		newpart->vel[1]=which->vel[1]+0.03*(float)rand()/RAND_MAX-0.015;
		newpart->vel[2]=which->vel[2]+0.03*(float)rand()/RAND_MAX-0.015;
		newpart->col[0]=which->col[0];//(float)rand()/RAND_MAX;
		newpart->col[1]=which->col[1];//(float)rand()/RAND_MAX;
		newpart->col[2]=which->col[2];//(float)rand()/RAND_MAX;
		newpart->age=0;
		newpart->maxage=500;
		newpart->type=SPARK;
		newpart->agefunc=Nothing;
		newpart->gen=which->gen+1;
		newpart->deathfunc=Nothing;
		newpart->hnext=NULL;
		newpart->hprev=NULL;
		HashPart(newpart, BORN);
		if(numspark>SPARKMAX) return;
	}
}

void XtdShower(particle *which){
	particle * newpart;
	//float r, g, b;
	int cmax=50;
	if(which->gen) cmax/=(which->gen*5);
	cmax=(cmax<1)?1:cmax;
	
	//r=(float)rand()/RAND_MAX;
	//g=(float)rand()/RAND_MAX;
	//b=(float)rand()/RAND_MAX;
	//create smoke
	if(!which->gen)
	for (int ctr=0; ctr<20; ctr++){
		numsmoke++;
		newpart=(particle *)malloc(sizeof(particle));
		newpart->newpos[0]=which->newpos[0]+1.*(float)rand()/RAND_MAX-0.5;
		newpart->newpos[1]=which->newpos[1]+1.*(float)rand()/RAND_MAX-0.5;
		newpart->newpos[2]=which->newpos[2]+1.*(float)rand()/RAND_MAX-0.5;
		if(newpart->newpos[0]>=XBUCKETMAX) newpart->newpos[0]=XBUCKETMAX-DELTA;
		if(newpart->newpos[0]<=XBUCKETMIN) newpart->newpos[0]=XBUCKETMIN+DELTA;
		if(newpart->newpos[1]>=YBUCKETMAX) newpart->newpos[1]=YBUCKETMAX-DELTA;
		if(newpart->newpos[1]<=YBUCKETMIN) newpart->newpos[1]=YBUCKETMIN+DELTA;
		if(newpart->newpos[2]>=ZBUCKETMAX) newpart->newpos[2]=ZBUCKETMAX-DELTA;
		if(newpart->newpos[2]<=ZBUCKETMIN) newpart->newpos[2]=ZBUCKETMIN+DELTA;
		newpart->oldpos[0]=0;
		newpart->oldpos[1]=0;
		newpart->oldpos[2]=0;
		newpart->vel[0]=which->vel[0]/20+0.01*(float)rand()/RAND_MAX-0.005;
		newpart->vel[1]=which->vel[1]/20+0.01*(float)rand()/RAND_MAX-0.005;
		newpart->vel[2]=which->vel[2]/20+0.01*(float)rand()/RAND_MAX-0.005;
		newpart->col[0]=0.2;
		newpart->col[1]=0.2;
		newpart->col[2]=0.2;
		newpart->age=0;
		newpart->maxage=5000;
		newpart->type=SMOKE;

		newpart->agefunc=Nothing;
		newpart->gen=which->gen+1;
		newpart->deathfunc=Nothing;
		newpart->hnext=NULL;
		newpart->hprev=NULL;
		HashPart(newpart, BORN);
		if(numsmoke>SMOKEMAX) break;
	}

	//create new particles
	for (int ctr=0; ctr<cmax; ctr++){
		numspark++;
		newpart=(particle *)malloc(sizeof(particle));
		newpart->newpos[0]=which->newpos[0];
		newpart->newpos[1]=which->newpos[1];
		newpart->newpos[2]=which->newpos[2];
		newpart->oldpos[0]=0;
		newpart->oldpos[1]=0;
		newpart->oldpos[2]=0;
		if(which->gen){
			newpart->vel[0]=which->vel[0]+0.01*(float)rand()/RAND_MAX-0.005;
			newpart->vel[1]=which->vel[1]+0.01*(float)rand()/RAND_MAX-0.005;
			newpart->vel[2]=which->vel[2]+0.01*(float)rand()/RAND_MAX-0.005;
		}else{
			newpart->vel[0]=which->vel[0]+0.03*(float)rand()/RAND_MAX-0.015;
			newpart->vel[1]=which->vel[1]+0.03*(float)rand()/RAND_MAX-0.015;
			newpart->vel[2]=which->vel[2]+0.03*(float)rand()/RAND_MAX-0.015;
		}
		newpart->col[0]=which->col[0];//(float)rand()/RAND_MAX;
		newpart->col[1]=which->col[1];//(float)rand()/RAND_MAX;
		newpart->col[2]=which->col[2];//(float)rand()/RAND_MAX;
		newpart->age=0;
		newpart->maxage=(200*(float)rand()/RAND_MAX+100)/((which->gen+1)*1);
		newpart->type=SPARK;
		newpart->agefunc=Nothing;
		newpart->gen=which->gen+1;
		if(numspark<SPARKMAX && newpart->gen<2) newpart->deathfunc=XtdShower;
		else newpart->deathfunc=Nothing;
		newpart->hnext=NULL;
		newpart->hprev=NULL;
		HashPart(newpart, BORN);
		if(numspark>SPARKMAX) return;
	}
}

// ident
// fills in thing with the identity matrix
//
void ident(float * thing)  //assumes 4x4 matrix of floats
{
	for (int ctr=0; ctr<16; ctr++) thing[ctr]=0;
	thing[0]=1; thing[5]=1; thing[10]=1; thing[15]=1;
}




// normalize
// takes a 3-vector and normalizes it
//
void normalize(float v[3])
{
	float l = v[0]*v[0] + v[1]*v[1] + v[2]*v[2];
	l = 1 / (float)sqrt(l);

	v[0] *= l;
	v[1] *= l;
	v[2] *= l;
}




// crossproduct
// crosses 2 3-vectors
//
void crossproduct(float a[3], float b[3], float res[3])
{
	res[0] = (a[1] * b[2] - a[2] * b[1]);
	res[1] = (a[2] * b[0] - a[0] * b[2]);
	res[2] = (a[0] * b[1] - a[1] * b[0]);
}




// length
// returns the length of a 3-vector
//
float length(float v[3])
{
	return (float)sqrt(v[0]*v[0] + v[1]*v[1] + v[2]*v[2]);
}



// myGlutIdle
// calculates animation stuff and refreshes the screen
//
void myGlutIdle(void)
{
	int x,y,z,ctr, cmax=0;
	particle *current=NULL, *next=NULL;
	float gravnow, windnow[3];

	// make sure the main window is active
	if (glutGetWindow() != main_window)
		glutSetWindow(main_window);
	
	// if you have moving objects, you can do that here
	timer2=clock();  //helps make machine independent animation speed
	cscaled=timer2-timer1;
	
	if(live_animate){
		scaledtot+=cscaled;
		scaledtot-=scaled[scaledpos];
		scaled[scaledpos]=cscaled;
		scaledpos++;
		if(scaledpos>=AVGN) scaledpos=0;
		if(scaledtot<=0) scaledtot=1;
		scaledavg=(float)scaledtot/AVGN;
		timer1=timer2;
		loopaccum+=scaledavg*speedfactor;
		if(loopaccum>=1){
			loop=loopaccum;
			loopaccum-=loop;
		}
		//linear+=cscaled;
		
		for(ctr=0; ctr<loop; ctr++)
		if(numfire<FIREDENSITY){
			numfire++;
			//consider firing a rocket
			particle * newpart;
			newpart=(particle *)malloc(sizeof(particle));
			newpart->newpos[0]=0;//5*(float)rand()/RAND_MAX-2.5;
			newpart->newpos[1]=0;
			newpart->newpos[2]=0;//5*(float)rand()/RAND_MAX-2.5;
			newpart->oldpos[0]=0;//newpart->newpos[0];
			newpart->oldpos[1]=0;//newpart->newpos[1];
			newpart->oldpos[2]=0;//newpart->newpos[2];
			newpart->vel[0]=0.03*(float)rand()/RAND_MAX-0.015;
			newpart->vel[1]=0.025*(float)rand()/RAND_MAX+0.025;
			newpart->vel[2]=0.03*(float)rand()/RAND_MAX-0.015;
			newpart->col[0]=(float)rand()/RAND_MAX;
			newpart->col[1]=(float)rand()/RAND_MAX;;
			newpart->col[2]=(float)rand()/RAND_MAX;;
			newpart->age=0;
			newpart->gen=0;
			newpart->maxage=500*(float)rand()/RAND_MAX+300;
			newpart->type=FIRE;
			newpart->agefunc=Nothing;
			newpart->deathfunc=XtdShower;
			newpart->hnext=NULL;
			newpart->hprev=NULL;
			HashPart(newpart, BORN);
		}

		//go through the particles and apply time changes
		for (x=0; x<XBUCKETS; x++)
		for (y=0; y<YBUCKETS; y++)
			for (z=0; z<ZBUCKETS; z++){
				current=parhash[x][y][z].list;
				cmax=parhash[x][y][z].num;
				for (ctr=0; ctr<cmax; ctr++){
					next=current->hnext;
					current->oldpos[0]=current->newpos[0];
					current->oldpos[1]=current->newpos[1];
					current->oldpos[2]=current->newpos[2];
					//age the particle
					//if(current->agefunc) (current->agefunc)();
					current->age+=loop;
					if(current->age > current->maxage){ //particle dies
						if(current->deathfunc) (current->deathfunc)(current);
						if(current->type==FIRE) numfire--;
						else if(current->type==SPARK) numspark--;
						else numsmoke--;
						HashPart(current, DIE);
						free(current);
					} else { //if not too old finish processing the particle
						//apply velocity (position change)
						current->newpos[0]+=scaledavg*speedfactor*current->vel[0];
						current->newpos[1]+=scaledavg*speedfactor*current->vel[1];
						current->newpos[2]+=scaledavg*speedfactor*current->vel[2];
						//IMPORTANT: if the particle leaves the hashing area kill it
						if(current->newpos[0]<=XBUCKETMIN ||
						current->newpos[0]>=XBUCKETMAX ||
						current->newpos[1]<=YBUCKETMIN ||
						current->newpos[1]>=YBUCKETMAX ||
						current->newpos[2]<=ZBUCKETMIN ||
						current->newpos[2]>=ZBUCKETMAX) {
							current->newpos[0]=0;
							current->newpos[1]=0;
							current->newpos[2]=0;
							if(current->type==FIRE) numfire--;
							else if(current->type==SPARK) numspark--;
							else numsmoke--;
							HashPart(current, DIE);
							free(current);
						} else {
							//rehash on the new pos
							HashPart(current, NORM);
							//apply gravity, wind, friction to the velocity
							gravnow=(current->type==SMOKE)?0:gravity;
							windnow[0]=(current->type==SMOKE)?wind[0]:wind[0]/4;
							windnow[1]=(current->type==SMOKE)?wind[1]:wind[1]/4;
							windnow[2]=(current->type==SMOKE)?wind[2]:wind[2]/4;
							current->vel[0]=(current->vel[0] + scaledavg*speedfactor*windnow[0]);
							current->vel[1]=(current->vel[1] + scaledavg*speedfactor*(windnow[1] - gravnow));
							current->vel[2]=(current->vel[2] + scaledavg*speedfactor*windnow[2]);
						}
					}
					current=next;
					//parhash[x][y][z].avgcol[0]=0;  //rgb
				}
			}
	}

	// just keep redrawing the scene over and over
	glutPostRedisplay();
}


// mouse handling functions for the main window
// left mouse translates, middle zooms, right rotates
// keep track of which button is down and where the last position was
int cur_button = -1;
int last_x;
int last_y;

// catch mouse up/down events
void myGlutMouse(int button, int state, int x, int y)
{
	if (state == GLUT_DOWN)
		cur_button = button;
	else
	{
		if (button == cur_button)
			cur_button = -1;
	}

	last_x = x;
	last_y = y;
}

// catch mouse move events
void myGlutMotion(int x, int y)
{
	// the change in mouse position
	int dx = x-last_x;
	int dy = y-last_y;

	float scale, len, theta;
	float neye[3], neye2[3];
	float f[3], r[3], u[3];

	switch(cur_button)
	{
	case GLUT_LEFT_BUTTON:
		// translate
		f[0] = lookat[0] - eye[0];
		f[1] = lookat[1] - eye[1];
		f[2] = lookat[2] - eye[2];
		u[0] = 0;
		u[1] = 1;
		u[2] = 0;

		// scale the change by how far away we are
		scale = sqrt(length(f)) * 0.007;

		crossproduct(f, u, r);
		crossproduct(r, f, u);
		normalize(r);
		normalize(u);

		eye[0] += -r[0]*dx*scale + u[0]*dy*scale;
		eye[1] += -r[1]*dx*scale + u[1]*dy*scale;
		eye[2] += -r[2]*dx*scale + u[2]*dy*scale;

		lookat[0] += -r[0]*dx*scale + u[0]*dy*scale;
		lookat[1] += -r[1]*dx*scale + u[1]*dy*scale;
		lookat[2] += -r[2]*dx*scale + u[2]*dy*scale;

		break;

	case GLUT_MIDDLE_BUTTON:
		// zoom
		f[0] = lookat[0] - eye[0];
		f[1] = lookat[1] - eye[1];
		f[2] = lookat[2] - eye[2];

		len = length(f);
		normalize(f);

		// scale the change by how far away we are
		len -= sqrt(len)*dy*-0.03;

		eye[0] = lookat[0] - len*f[0];
		eye[1] = lookat[1] - len*f[1];
		eye[2] = lookat[2] - len*f[2];

		// make sure the eye and lookat points are sufficiently far away
		// push the lookat point forward if it is too close
		if (len < 1)
		{
			printf("lookat move: %f\n", len);
			lookat[0] = eye[0] + f[0];
			lookat[1] = eye[1] + f[1];
			lookat[2] = eye[2] + f[2];
		}

		break;

	case GLUT_RIGHT_BUTTON:
		// rotate

		neye[0] = eye[0] - lookat[0];
		neye[1] = eye[1] - lookat[1];
		neye[2] = eye[2] - lookat[2];

		// first rotate in the x/z plane
		theta = -dx * 0.007;
		neye2[0] = (float)cos(theta)*neye[0] + (float)sin(theta)*neye[2];
		neye2[1] = neye[1];
		neye2[2] =-(float)sin(theta)*neye[0] + (float)cos(theta)*neye[2];


		// now rotate vertically
		theta = -dy * 0.007;

		f[0] = -neye2[0];
		f[1] = -neye2[1];
		f[2] = -neye2[2];
		u[0] = 0;
		u[1] = 1;
		u[2] = 0;
		crossproduct(f, u, r);
		crossproduct(r, f, u);
		len = length(f);
		normalize(f);
		normalize(u);

		neye[0] = len * ((float)cos(theta)*f[0] + (float)sin(theta)*u[0]);
		neye[1] = len * ((float)cos(theta)*f[1] + (float)sin(theta)*u[1]);
		neye[2] = len * ((float)cos(theta)*f[2] + (float)sin(theta)*u[2]);

		eye[0] = lookat[0] - neye[0];
		eye[1] = lookat[1] - neye[1];
		eye[2] = lookat[2] - neye[2];

		break;
	}


	last_x = x;
	last_y = y;

	glutPostRedisplay();
}

// you can put keyboard shortcuts in here
void myGlutKeyboard(unsigned char key, int x, int y)
{
	switch(key)
	{
	// quit
	case 27: 
	case 'q':
	case 'Q':
		exit(0);
		break;
	case 'p':
		//printf("%g %g | %g %g %g | %g %g %g\n", anglex, angley, eye[0], eye[1], eye[2], rspherexz[0], rspherey, rspherexz[1]);
		break;
	}

	glutPostRedisplay();
}

// the window has changed shapes, fix ourselves up
void myGlutReshape(int	x, int y)
{
	int tx, ty, tw, th;
	GLUI_Master.get_viewport_area(&tx, &ty, &tw, &th);
	glViewport(tx, ty, tw, th);

	glutPostRedisplay();
}

#define FLOORSEC 10
#define FLOORD 20.0f
#define FLOORSW FLOORD/FLOORSEC
void DrawFloor(){
	float lightpos[]={lightxz[0], lighty, -lightxz[1],1};
	int ctr1, ctr2;
	float cl, cr, cu, cd, tl, tr, tu, td;
	
	glDisable(GL_BLEND);
	glDisable(GL_LIGHTING);
	//glEnable(GL_TEXTURE_2D);
	//glBindTexture(GL_TEXTURE_2D, textures[0]);
	//glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
			
	for (ctr1=0; ctr1<FLOORSEC; ctr1++)
	{
		for(ctr2=0; ctr2<FLOORSEC; ctr2++)
		{
			cl=(ctr1*FLOORSW)-FLOORD/2; cr=((ctr1+1)*FLOORSW)-FLOORD/2;
			cu=(ctr2*FLOORSW)-FLOORD/2; cd=((ctr2+1)*FLOORSW)-FLOORD/2;
			tl=((float)ctr1/FLOORSEC); tr=tl+1.0f/FLOORSEC;
			tu=((float)ctr2/FLOORSEC); td=tu+1.0f/FLOORSEC;

			//Draw the floor
			glBegin(GL_QUADS);
			glNormal3f(0,1,0);
			glColor3f(tr, tu, 0);
			glTexCoord2f(tr, tu); glVertex3f(cr, 0, cu);
			glColor3f(tl, tu, 0);
			glTexCoord2f(tl, tu); glVertex3f(cl, 0, cu);
			glColor3f(tl, td, 0);
			glTexCoord2f(tl, td); glVertex3f(cl, 0, cd);
			glColor3f(tr, td, 0);
			glTexCoord2f(tr, td); glVertex3f(cr, 0, cd);
			glEnd();
		}
	}
	glEnable(GL_LIGHTING);
	//glDisable(GL_TEXTURE_2D);
}

void DrawSkyBox(int cx, int cy, int cz, int rx, int ry, int rz,
				int ft, int bk, int lt, int rt, int up, int dn){
	glColor3f(1,1,1);
	glEnable(GL_TEXTURE_2D);
	glEnable(GL_CULL_FACE);
	glDisable(GL_LIGHTING);
	glBindTexture(GL_TEXTURE_2D, ft);
	glBegin(GL_QUADS);
		glTexCoord2d(1,1); glVertex3f(cx-rx, cy+ry, cz+rz);  //upper right
		glTexCoord2d(0,1); glVertex3f(cx+rx, cy+ry, cz+rz);  //upper left
		glTexCoord2d(0,0); glVertex3f(cx+rx, cy-ry, cz+rz);  //lower left
		glTexCoord2d(1,0); glVertex3f(cx-rx, cy-ry, cz+rz);  //lower right
	glEnd();
	glBindTexture(GL_TEXTURE_2D, bk);
	glBegin(GL_QUADS);
		glTexCoord2d(1,1); glVertex3f(cx+rx, cy+ry, cz-rz);  //upper right
		glTexCoord2d(0,1); glVertex3f(cx-rx, cy+ry, cz-rz);  //upper left
		glTexCoord2d(0,0); glVertex3f(cx-rx, cy-ry, cz-rz);  //lower left
		glTexCoord2d(1,0); glVertex3f(cx+rx, cy-ry, cz-rz);  //lower right
	glEnd();
	glBindTexture(GL_TEXTURE_2D, lt);
	glBegin(GL_QUADS);
		glTexCoord2d(1,1); glVertex3f(cx-rx, cy+ry, cz-rz);  //upper right
		glTexCoord2d(0,1); glVertex3f(cx-rx, cy+ry, cz+rz);  //upper left
		glTexCoord2d(0,0); glVertex3f(cx-rx, cy-ry, cz+rz);  //lower left
		glTexCoord2d(1,0); glVertex3f(cx-rx, cy-ry, cz-rz);  //lower right
	glEnd();
	glBindTexture(GL_TEXTURE_2D, rt);
	glBegin(GL_QUADS);
		glTexCoord2d(1,1); glVertex3f(cx+rx, cy+ry, cz+rz);  //upper right
		glTexCoord2d(0,1); glVertex3f(cx+rx, cy+ry, cz-rz);  //upper left
		glTexCoord2d(0,0); glVertex3f(cx+rx, cy-ry, cz-rz);  //lower left
		glTexCoord2d(1,0); glVertex3f(cx+rx, cy-ry, cz+rz);  //lower right
	glEnd();
	glBindTexture(GL_TEXTURE_2D, up);
	glBegin(GL_QUADS);
		glTexCoord2d(0,0); glVertex3f(cx+rx, cy+ry, cz+rz);  //upper right
		glTexCoord2d(1,0); glVertex3f(cx-rx, cy+ry, cz+rz);  //upper left
		glTexCoord2d(1,1); glVertex3f(cx-rx, cy+ry, cz-rz);  //lower left
		glTexCoord2d(0,1); glVertex3f(cx+rx, cy+ry, cz-rz);  //lower right
	glEnd();
	glBindTexture(GL_TEXTURE_2D, dn);
	glBegin(GL_QUADS);
		glTexCoord2d(1,1); glVertex3f(cx+rx, cy-ry, cz-rz);  //upper right
		glTexCoord2d(0,1); glVertex3f(cx-rx, cy-ry, cz-rz);  //upper left
		glTexCoord2d(0,0); glVertex3f(cx-rx, cy-ry, cz+rz);  //lower left
		glTexCoord2d(1,0); glVertex3f(cx+rx, cy-ry, cz+rz);  //lower right
	glEnd();
	glDisable(GL_TEXTURE_2D);
	glEnable(GL_LIGHTING);
}

//this was copied from the web
void renderBitmapString(float x, float y, float z, void *font, char *string) {  
	char *c;
	glRasterPos3f(x, y,z);
	for (c=string; *c != '\0'; c++) {
		glutBitmapCharacter(font, *c);
	}
}

void FaceCamera(){
	float tfm[16];
	glGetFloatv(GL_MODELVIEW_MATRIX, tfm);

	//kill the rotations
	tfm[0]=1;
	tfm[1]=0;
	tfm[2]=0;
	tfm[4]=0;
	tfm[5]=1;
	tfm[6]=0;
	tfm[8]=0;
	tfm[9]=0;
	tfm[10]=1;

	glLoadMatrixf(tfm);
}

// draw the scene
void myGlutDisplay(	void )
{
	//char temp[20];
	int x,y,z,ctr;
	parbkt * curhash;
	particle * current=NULL, *next=NULL;

	float lightpos[]={lightxz[0], lighty, lightxz[1],1};
	glClearColor(0, 0.12, 0.17, 0);
	glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

	// projection transform
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glFrustum(-1, 1, -1, 1, 1, 1000);

	// camera transform
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	gluLookAt(eye[0], eye[1], eye[2], lookat[0], lookat[1], lookat[2], up[0], up[1], up[2]);
	
	//draw the groud
	//DrawFloor();
	DrawSkyBox(0,20,0,100,100,100,textures[2], textures[4], textures[3], textures[5], textures[6], textures[7]);
	
	//draw the firework particles
	glEnable(GL_TEXTURE_2D);
	glEnable(GL_BLEND);
	glDisable(GL_DEPTH_TEST);
	glColor3f(1,1,1);
	glDisable(GL_LIGHTING);
	for (x=0; x<XBUCKETS; x++)
	for (y=0; y<YBUCKETS; y++)
	for (z=0; z<ZBUCKETS; z++){
		curhash=&parhash[x][y][z];
		current=curhash->list;
		for (ctr=0; ctr<parhash[x][y][z].num; ctr++){
			next=current->hnext;
			if(current->type==SMOKE){
				glBindTexture(GL_TEXTURE_2D, textures[1]);
				glColor3f(current->col[0]+curhash->avgcol[0][0][0][0]/4,
					current->col[1]+curhash->avgcol[0][0][0][1]/4,
					current->col[2]+curhash->avgcol[0][0][0][2]/4); 
			} else {
				glBindTexture(GL_TEXTURE_2D, textures[0]);
				glColor3f(current->col[0],current->col[1],current->col[2]); 
			}
			glPushMatrix();
				glTranslatef(current->newpos[0], current->newpos[1], current->newpos[2]);
				FaceCamera();
				int sz=(current->type==SMOKE)?20:1;
				glBegin(GL_QUADS);
					glTexCoord2f(0,0); glVertex3f(sz*.1,sz*.1,0);
					glTexCoord2f(1,0); glVertex3f(sz*-.1,sz*.1,0);
					glTexCoord2f(1,1); glVertex3f(sz*-.1,sz*-.1,0);
					glTexCoord2f(0,1); glVertex3f(sz*.1,sz*-.1,0);
				glEnd();
			glPopMatrix();
			current=next;
		}
	}
	glDisable(GL_BLEND);
	glDisable(GL_TEXTURE_2D);
	glEnable(GL_LIGHTING);
	glEnable(GL_DEPTH_TEST);

	//draw smoke particles
	//renderBitmapString(0,0,0,GLUT_BITMAP_8_BY_13 ,itoa(numspark, temp, 10));
	//renderBitmapString(0,0,0,GLUT_BITMAP_8_BY_13 , fcvt(scaledavg, 10, &x, &y));
	
	glutSwapBuffers();
}

// some controls generate a callback when they are changed
void glui_cb(int control)
{
	
	int wobj=object_choice->get_int_val();
	switch(control)
	{
	case CB_OBJ_CHOICE:
		switch(wobj)
		{
		case 1:
			break;
		case 2:
			object_rotation->disable();
			object_xz_trans->set_float_array_val(lightxz);
			object_y_trans->set_float_array_val(&lighty);
			break;
		}
		break;
	case CB_ROT:
		break;
	case CB_XZ:
		switch(wobj)
		{
		case 1:
			break;
		case 2:
			object_xz_trans->get_float_array_val(lightxz);
			break;
		}
		break;
	case CB_Y:
		switch(wobj)
		{
		case 1:
			break;
		case 2:
			object_y_trans->get_float_array_val(&lighty);
			break;
		}
		break;
	case WINDX_CB:
		wind[0]=windx_spinner->float_val/100000;
		break;
	case WINDY_CB:
		wind[1]=windy_spinner->float_val/100000;
		break;
	case WINDZ_CB:
		wind[2]=windz_spinner->float_val/100000;
		break;
	}
	
	glutPostRedisplay();
}

void reset(){
	lightxz[0]=LIGHTX; lightxz[1]=LIGHTZ; lighty=LIGHTY;
	eye[0] = EPX;
	eye[1] = EPY;
	eye[2] = EPZ;
	lookat[0] = LAX;
	lookat[1] = LAY;
	lookat[2] = LAZ;
	up[0] = UPX;
	up[1] = UPY;
	up[2] = UPZ;
}

// entry point
void main(int argc,	char* argv[])
{
	//int wmc, wmx, wmy;
	char tname[10];
	char tname2[3];
	
	//setup defaults in case of no command line
	screenx=1000; screeny=600;

	//
	// create the glut window
	//
	glutInitDisplayMode(GLUT_RGB|GLUT_DOUBLE|GLUT_DEPTH);
	glutInitWindowSize(screenx, screeny);
	glutInitWindowPosition(100,100);
	main_window = glutCreateWindow("Sample Interface");
	GLenum err = glewInit();
	if (GLEW_OK != err)
	{
		// problem: glewInit failed, something is seriously wrong 
		fprintf(stderr, "Error: %s\n", glewGetErrorString(err));
		exit(0);
	}
	fprintf(stdout, "Status: Using GLEW %s\n", glewGetString(GLEW_VERSION));


	//
	// set callbacks
	//
	glutDisplayFunc(myGlutDisplay);
	GLUI_Master.set_glutReshapeFunc(myGlutReshape);
	GLUI_Master.set_glutIdleFunc(myGlutIdle);
	GLUI_Master.set_glutKeyboardFunc(myGlutKeyboard);
	GLUI_Master.set_glutMouseFunc(myGlutMouse);
	glutMotionFunc(myGlutMotion);


	//
	// create the interface subwindow and add widgets
	//
	glui = GLUI_Master.create_glui_subwindow(main_window, GLUI_SUBWINDOW_LEFT);


	// initialize live variables
	live_object_xz_trans[0] = 0;
	live_object_xz_trans[1] = 0;
	live_object_y_trans = 0;
	lightxz[0]=LIGHTX; lightxz[1]=LIGHTZ; lighty=LIGHTY;
	// quit button
	glui->add_button("Quit", 0, (GLUI_Update_CB)exit);

	// empty space
	glui->add_statictext("");

	glui->add_button("Reset", 0, (GLUI_Update_CB)reset);

	// the object rollout
	object_rollout = glui->add_rollout("Object");
	object_choice = glui->add_listbox_to_panel(object_rollout, "Which object?", 0, CB_OBJ_CHOICE, glui_cb);
	object_choice->add_item(1, "wind");
	object_choice->add_item(2, "light");

	// rotation and translation controls
	// we need an extra panel to keep things grouped properly
	GLUI_Panel *transform_panel = glui->add_panel_to_panel(object_rollout, "", GLUI_PANEL_NONE);
	object_rotation = glui->add_rotation_to_panel(transform_panel, "Rotation", 0, CB_ROT, glui_cb);
	object_rotation->reset();

	glui->add_column_to_panel(transform_panel, false);
	object_xz_trans = glui->add_translation_to_panel(transform_panel, "XZ", 
													 GLUI_TRANSLATION_XY, 0, CB_XZ, glui_cb);

	glui->add_column_to_panel(transform_panel, false);
	object_y_trans =  glui->add_translation_to_panel(transform_panel, "Y", 
													 GLUI_TRANSLATION_Y, 0, CB_Y, glui_cb);

	glui->add_statictext("");
	glui->add_checkbox("Animate", &live_animate);
	
	//glui->add_statictext("");
	//glui->add_checkbox("Draw Wireframe", &live_wireframe);
	
	glui->add_statictext("");
	speed_spinner = glui->add_spinner("Speed", 3, &speedfactor, WRIGGLES_CB, glui_cb);
	speed_spinner->set_float_limits(0.0001, 1);
	
	glui->add_statictext("");
	num_spinner = glui->add_spinner("Number firing", 2, &FIREDENSITY, WRIGGLES_CB, glui_cb);
	num_spinner->set_int_limits(0, 500, 1);

	wind[0]=1;
	glui->add_statictext("");
	windx_spinner = glui->add_spinner("Wind x", 3, NULL, WINDX_CB, glui_cb);
	windx_spinner->set_float_limits(-1.5, 1.5);
	windy_spinner = glui->add_spinner("Wind y", 3, NULL, WINDY_CB, glui_cb);
	windy_spinner->set_float_limits(-1.5, 1.5);
	windz_spinner = glui->add_spinner("Wind z", 3, NULL, WINDZ_CB, glui_cb);
	windz_spinner->set_float_limits(-1.5, 1.5);
	
	glui->set_main_gfx_window(main_window);

	// initialize the camera
	eye[0] = EPX;
	eye[1] = EPY;
	eye[2] = EPZ;
	lookat[0] = LAX;
	lookat[1] = LAY;
	lookat[2] = LAZ;
	up[0] = UPX;
	up[1] = UPY;
	up[2] = UPZ;

	// initialize gl
	glClearColor (0.0, 0.0, 0.0, 0.0);
	glShadeModel (GL_SMOOTH);
	glLightfv(GL_LIGHT0, GL_POSITION, light_position);
	glLightf(GL_LIGHT0, GL_LINEAR_ATTENUATION, 0.0001f);
	glLightf(GL_LIGHT0, GL_QUADRATIC_ATTENUATION, 0.0001f);
	glEnable(GL_NORMALIZE);
	glCullFace(GL_BACK);
	glEnable(GL_COLOR_MATERIAL);
	glEnable(GL_CULL_FACE);
	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0);
	glEnable(GL_DEPTH_TEST);
	glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glLightModeli(GL_LIGHT_MODEL_COLOR_CONTROL, GL_SEPARATE_SPECULAR_COLOR);
	glPointSize(3);
	//textures
	glGenTextures(NUMTEX, textures);
	glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

	//bump map textures (fireworks)
	for(int ctr=0; ctr<NUMTEX; ctr++){
		strcpy(tname, "fire"); strcat(tname, itoa(ctr+1, tname2, 10)); strcat(tname, ".rgb");
		texref[ctr].ptr=read_rgb(tname, &texref[ctr].xdim, &texref[ctr].ydim , &texref[ctr].cdim);
		glBindTexture(GL_TEXTURE_2D, textures[ctr]);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		//gluBuild2DMipmaps(GL_TEXTURE_2D, wood1dep, wood1x, wood1y, GL_RGBA, GL_UNSIGNED_BYTE, woodtex);
		glTexImage2D(GL_TEXTURE_2D, 0, texref[ctr].cdim, texref[ctr].xdim, texref[ctr].ydim, 0, GL_RGBA, GL_UNSIGNED_BYTE, texref[ctr].ptr);
	}
	InitHash();
	// give control over to glut
	glutMainLoop();
}