/*
 * Simulation of particle's interaction.
 * Version 0.1.
 * #include <GPL-disclaimer>
 */

/*
 * LibV contains all usefull stuff I’m using here; someday (when it matures a bit)
 * I will put it on ftp://gasa.penza.com.ru/pub/freeware/libv
 * If your, reader, REALLY want to compile this, email me :)
 */
#include <v_lib.h>
#include "ljsetup.h"
#include "ljutil.h"

/*
 * Uncomment next line to enable anti-aliased lines.
 * Note: AA not works properly under current XFree86 DRI
 * drivers (at least, on my Radeon QD :). With NV card
 * all is Ok on both platforms (Win32 and Linux)
#define	AA_ENABLED
*/

/*
 * -----------------------------------------------------------------------
 *              Part 1: model of system being investigated
 * -----------------------------------------------------------------------
 */

typedef struct particle_s
{
	vec_t	radius;	/* radius of particle */
	vec_t	_6_pi_eta_r;	/* viscous resistance */
	vec_t	mass;	/* gravital mass: 4/3 * Pi * radius^3 * density */
	vec_t	rm;		/* reciprocal inertial mass (1/mass) */
}	particle_t;

#define		NUM_OBJECTS		15
#define		MAX_OBJECTS		1000

/*
 * number of time-shots per second, used by batch solver
 */
u32_t	FPS				 = 10;

/*
 * Deep of minima on the potential curve
 */
vec_t	U0			= -1e-13;
/*
 * Radius and density of the particles
 */
vec_t	OBJ_R		= 1e-6;
vec_t	density 	= 2500;
/*
 * Distance of equilibrium (measured between particles' surfaces)
 */
vec_t	r0			= 1e-6;
/*
 * Viscousity
 */
vec_t	ETA			= 1;
/*
 * Maximal distance from origin in initial state
 */
vec_t				POS_MAX	= 2e-5;
/*
 * Properties of the potential barrier
 */
vec_t		potential_barrier_width		= 1e-4;
vec_t		potential_barrier_height	= 1e-7;

/*
 * Static array shared between 'solver' and 'user'
 */
particle_t	obj[MAX_OBJECTS];
vec_t		pos_vel[MAX_OBJECTS * 6];	/* position and velocity of the particles */

vec_t		simulation_time;

/*
 * We force particles to be close to each other by introducing
 * portential barrier. If particle falls out of domain, we add extra
 * force directed such way that particle moves towards domain's center.
 *
 * There are a number of ways to do this.
 *  	1. The most intuitive 'parabolic' potential which has explicit
 *		physical analogy (elastic force):
 *			U = 0, r <= r_b;
 *			U = \frac{k}{2} * (r-r_b)^2, r > r_b;
 *			\bold{F} = 0, r <= r_b;
 *			\bold{F} = - k * \bold{r} * (1-\frac{r_b}{r}), r > r_b,
 *		where:
 *			\bold{F} - force, acting on particle;
 *			k - elasticity constant;
 *			\bold{r}, r - radius-vector and it modulus, respectively;
 *			r_b - radius of potential barrier.
 *		2. More restrictive 'exponential' potential:
 *			U = 0, r <= r_b;
 *			U = k * (cosh(r-r_b)-1), r > r_b;
 *			\bold{F} = 0, r <= r_b;
 *			\bold{F} = - k * \frac{\bold{r}}{r} * sinh(r-r_b), r > r_b.
 *		3. The above cases are axial-symmetric. We can also use rectangular
 *	potential function:
 *			\bold{F} = 0, r is inside box;
 *			\bold{F} = k * \bold{n} * len_from_wall,
 *		where:
 *			\bold{n} - vector drawn from outside box (perpendicular to wall);
 *			len_from_wall - distance from wall.
 */

vboolean strafe_parabolic(vec3_t frc,vec3_t pos)
{
	vec_t r = vec3Abs(pos);
	if(r < potential_barrier_width)
		return false;
	vec3Scale(frc,pos,
		potential_barrier_height * (potential_barrier_width - r));
	return true;
}

V_LOCAL vec_t hyperbolic_sine(vec_t x)
{
	return (vec_t)0.5 * (vec_t)(exp(x) - exp(-x));
}

vboolean strafe_sinh(vec3_t frc,vec3_t pos)
{
	vec_t r = vec3Abs(pos);
	if(r < potential_barrier_width)
		return false;
	vec3Scale(frc,pos,potential_barrier_height 
		* hyperbolic_sine(potential_barrier_width - r) / r);
	return true;
}

vboolean strafe_rectangular(vec3_t frc,vec3_t pos)
{
	int i;
	vboolean outside = false;
	for(i=0;i<3;i++)
	{
		if(pos[i] < -potential_barrier_width)
		{
			frc[i] = (-potential_barrier_width - pos[i]) * potential_barrier_height; /* positive */
		} else if(pos[i] > potential_barrier_width)
		{
			frc[i] = (potential_barrier_width - pos[i]) * potential_barrier_height; /* negative */
		} else
		{
			frc[i] = 0;
			continue;
		}
		outside = true;
	}
	return outside;
}

/*
 * For given distance, returns force modulus.
 * Assumes that particles' interaction can be described by
 * Lennard-Jones potential:
 * U=U_0((\frac{r_0}{r})^{12}-2(\frac{r_0}{r})^{6}),
 * where:
 *  U_0 - modulus of energy at minimum;
 *  r_0 - position of minimum;
 *  r - current position.
 * Force calculated as: \frac{d}{dr}(U(r))|_{r=dist}
 * Input:
 *  dist - distance to use in force(distance).
 */
static vec_t
LJ_modulus(vec_t dist)
{
	vec_t rd = (vec_t)1.0 / dist; /* reciprocal distance */
	vec_t r0r = r0 * rd;
	vec_t p6;
	/*
	 * First derivative of Lennard - Jones potential:
	 * d/dr(U0*((r0/r)^12 - 2*(r0/r)^6))
	 */
	p6 = r0r * r0r * r0r;
	p6 *= p6;
	return 12 * U0 * rd * p6 * (1 - p6);
}

/*
 * Calculates force acting from all particles to particle #index.
 * \bold{F} = \sum_i\frac{\bold{r}_i * LJ_modulus(|\bold{r}_i|-(s1+s2))}{|\bold{r}_i|},
 * where:
 *  \bold{r}_i - vector drawn from particle #index to partile #i;
 *  s1, s2 - sizes of particles #i, #index.
 */

#define MIN_DIST 1e-15 /* minimal distance for LJ_modulus */


void LJ_force(vec3_t force_ret,int index,vec_t tm /* unused */,vec_t pos_vel[])
{
	int i;
	vec3_t dr,df;
	vec_t r, ar, f;
	vec_p pos, self_pos = &pos_vel[index * 6]; /* read-only */
	vec3Zero(force_ret);
	for(i=0;i<NUM_OBJECTS;i++)
	{
		if(i == index)
			continue;
		pos = &pos_vel[i * 6];
		vec3Sub(dr,pos,self_pos);
		ar = vec3Abs(dr);
		r = ar - (obj[index].radius + obj[i].radius);
		if(r < MIN_DIST)
			r = MIN_DIST;
		f = LJ_modulus(r);
		vec3Scale(df,dr,f / ar);
		vec3Add(force_ret,force_ret,df);
	}
}

/*
 * Calculates force acting from environment to particle #index.
 * \bold{F} = 6*\pi*\eta*r*(\bold{v}-\bold{V}) + m*\bold{g} + B_SUM,
 * B_SUM = \sum_j\B_STRAFE_j
 * B_STRAFE_j = LJ_modulus(\bold{r} * \bold{n}_j - d_j -s), if \bold{r} * \bold{n}_j - d_j > MIN_DIST;
 * B_STRAFE_j = predefined(\bold{r} * \bold{n}_j - d_j -s), otherwise,
 * where:
 * eta - viscousity of the environment;
 * \bold{v} - velosity of the particle;
 * \bold{V} - velosity of the environment;
 * \bold{r} - position of the particle;
 * \bold{n}_j, d_j - normal equation of the jth boundary plane;
 * s - size of the particle.
 * predefined strafe function for out-of-boundary particles.
 */
void
ENV_force(vec3_t force_ret,int index,vec_t tm,vec_t pos_vel[],
	void (*env_vel)(vec3_t V_ret,int index,vec_t tm,vec_t pos_vel[]), /* env velosity */
	vec3_t g_accel, /* fall acceleration */
	vboolean use_gravity, /* set to true to include m*\bold{g} into result */
	plane3_p bplanes, /* array of the boundary planes */
	int nplanes, /* number of the boundary planes */
	/* strafe function for out-of-bounds particle */
	vec_t (*ob_strafe)(vec_t ob_dist,int index,vec_t tm,vec_t pos_vel[])
	) 
{
	int i;
	vec3_t V, temp;
	
	vec_t d;
	vec_p r = &pos_vel[index * 6]; /* read-only */
	vec_p v = &pos_vel[index * 6 + 3]; /* read-only */
	vec3Zero(force_ret);
	
	(*env_vel)(V,index,tm,pos_vel);
	vec3Sub(force_ret,V,v);
	vec3Scale(force_ret,force_ret,obj[index]._6_pi_eta_r);
	
	if(use_gravity)
	{
		vec3Scale(temp,g_accel,obj[index].mass);
		vec3Add(force_ret,force_ret,temp);
	}
	
	for(i=0;i<nplanes;i++)
	{
		d = vec3Dot(bplanes[i].normal,r) - bplanes[i].dist - obj[index].radius;
		if(d > MIN_DIST)
		{
			/* assume |bplanes[i].normal| == 1 */
			vec3Scale(temp,bplanes[i].normal,LJ_modulus(d));
			vec3Add(force_ret,force_ret,temp);
		}
		else if(d < 0 && ob_strafe != NULL)
		{
			vec3Scale(temp,bplanes[i].normal,(*ob_strafe)(d,index,tm,pos_vel));
			vec3Add(force_ret,force_ret,temp);
		}
		
	}
}
/*
 * Returns velosity of the environment
 */

void env_vel_zero(vec3_t V_ret,int index,vec_t tm,vec_t pos_vel[])
{
	vec3Zero(V_ret); /* TODO */
}

/*
 * Total force computation
 */

vboolean (*EXTRA_force)(vec3_t frc,vec3_t pos) = strafe_parabolic;
void (*env_vel)(vec3_t V_ret,int index,vec_t tm,vec_t pos_vel[]) = env_vel_zero;

void compute_force(vec3_t force_ret,int index,vec_t tm,vec_t pos_vel[])
{
	vec3_t g = {0,0,-9.8};
	vec_p pos = &pos_vel[index * 6];
	vec3_t temp;
	LJ_force(force_ret,index,tm,pos_vel);
	ENV_force(temp,index,tm,pos_vel,env_vel_zero,g,false,NULL,0,NULL);
	vec3Add(force_ret,force_ret,temp);
	if(EXTRA_force(temp,pos))
		vec3Add(force_ret,force_ret,temp);
}
 
/*
 * -----------------------------------------------------------------------
 * Part 2: intial conditons
 */


#if 0
V_LOCAL void regular_pos(vec3_t pos_ret)
{
	/*
	static int count = 0;
	vec_t phase = M_2PI * (vec_t)(count++) / (vec_t)(NUM_OBJECTS);
	pos_ret[0] = 2 * cos(phase);
	pos_ret[1] = 2 * sin(phase);
	pos_ret[2] = frand(MIN_Z,MAX_Z);
	*/
}
#endif

/*
 * Returns 'true' if 'pos' is away to 'min_dist' from objects' 0..n-1 surfaces
 */
vboolean is_away(vec3_t pos,vec_t min_dist,u32_t n)
{
	u32_t i;
	vec_t d;
	vec3_t dist;
	for(i=0;i<n;i++)
	{
		vec3Sub(dist,&pos_vel[i * 6],pos);
		d = vec3Abs(dist) - obj[i].radius - obj[n-1].radius;
		if(d < min_dist)
			return false;
	}
	return true;
}
 
/*
 * Routine to call from solver_init()
 */
void problem_initial_value(void)
{
	int i, count;
	vec3_t mins, maxs;
	do
	{
	    for(i=0;i<NUM_OBJECTS;i++)
    	{
			obj[i].radius = OBJ_R;
			count = 0;
    		do
    		{
				vec3Rand(&pos_vel[i * 6],-POS_MAX,POS_MAX);
				/* obj[i].pos[2] = 0; */
				if(++count > 1000)
					break;
			}	while(!is_away(&pos_vel[i * 6],2 * r0,i));
			vec3Set(&pos_vel[i * 6 + 3],0,0,0);
		}
		/* if(count < 1000) */
		/* Compute bounding box */
		bboxClear(mins,maxs);
		for(i=0;i<NUM_OBJECTS;i++)
			bboxAddPoint(mins,maxs,&pos_vel[i * 6]);
		vec3Sub(maxs,maxs,mins);
	} while(vec3Abs(maxs) < 1e-18);
	/* setup gravital, reciprocal inertial masses and viscousity coefficients */
    for(i=0;i<NUM_OBJECTS;i++)
    {
		obj[i].mass = 4.0 / 3.0 * M_PI * obj[i].radius * obj[i].radius * obj[i].radius * density;
		obj[i].rm = 1.0 / obj[i].mass;
		obj[i]._6_pi_eta_r = 6 * M_PI * ETA * obj[i].radius;
	}
}

/*
 * Routine to call from the ODE solver
 */
void V_CALL problem_right_side(vec_t deriv[],vec_t t,vec_t x[])
{
	vec_t fc;
	vec3_t tmp;
	vec3_t frc;
	vec_t *pos = x, *vel = x+3;
	vec_t *pos_deriv = deriv, *vel_deriv = deriv+3;
	int index;
	for(index=0;index<NUM_OBJECTS;index++)
	{
		compute_force(frc,index,t,x);
		vec3Scale(vel_deriv,frc,obj[index].rm);
		vec3Copy(pos_deriv,vel);
		pos += 6;
		vel += 6;
		pos_deriv += 6;
		vel_deriv += 6;
	}
}

/*
 * -----------------------------------------------------------------------
 * Part 4: drawing stuff
 */
typedef struct drawstate_s
{
	GLfloat rotx, rotz;
	GLfloat zoom, vdx, vdy, vdz;
	vboolean inner_grid, outer_grid, ortho;
	int lod;
}	drawstate_t, *drawstate_p;

V_LOCAL drawstate_t drawstate;

V_LOCAL void draw_circle(GLfloat r,int segs)
{
	int i;
	GLfloat phi = 0;
	GLfloat dphi = (GLfloat)(M_2PI / segs);
	GLfloat x,y;
	glBegin(GL_LINE_LOOP);
	for(i=0;i<segs;i++,phi+=dphi)
	{
		x = r * (GLfloat)cos(phi);
		y = r * (GLfloat)sin(phi);
		glVertex2f(x,y);
	}
	glEnd();
}

V_LOCAL void draw_3circles(GLfloat r,int segs)
{
	draw_circle(r,segs);
	glRotatef(90,1,0,0);
	draw_circle(r,segs);
	glRotatef(90,0,1,0);
	draw_circle(r,segs);
}

#define	INNER_SEGS		16
#define	OUTER_SEGS		32

V_LOCAL void draw_particle(int lod,vec_p pos,vec_t radius,vec_t r_minU)
{
	glPushMatrix();
	glTranslated(pos[0],pos[1],pos[2]);
	if(lod > 0)
	{
		glColor4f(1,1,1,0.65f);
		draw_3circles((GLfloat)radius,INNER_SEGS);
	}
	if(lod > 1)
	{
		glColor4f(0,1,1,0.5f);
		draw_3circles((GLfloat)r_minU,OUTER_SEGS);
	}
	glPopMatrix();
}

V_LOCAL void draw_xOy_grid(vec3_t origin,vec_t width,vec_t height,u32_t num_lines)
{
	u32_t i;
	vec3_t p, dp;
	vec3Copy(p,origin);
	p[0] -= (vec_t)0.5 * width;
	p[1] -= (vec_t)0.5 * height;
	vec3Set(dp,width,height,0);
	vec3Scale(dp,dp,(vec_t)1 / (num_lines-1));
	glBegin(GL_LINES);
		/* grid with lines along Oy direction */
		for(i=0;i<num_lines;i++)
		{
			VGL_VERTEX3(p[0]+i*dp[0],p[1],p[2]);
			VGL_VERTEX3(p[0]+i*dp[0],p[1]+height,p[2]);
		}
		/* grid with lines along Ox direction */
		for(i=0;i<num_lines;i++)
		{
			VGL_VERTEX3(p[0],p[1]+i*dp[1],p[2]);
			VGL_VERTEX3(p[0]+width,p[1]+i*dp[1],p[2]);
		}
	glEnd(); /* GL_LINES */
}

V_LOCAL void draw_2_xOy_grids(vec3_t origin,vec3_t center,vec_t width,vec_t height,u32_t major_lines,u32_t minor_lines)
{
	vec_t dw, dh;
	vec3_t neworg;
	if(minor_lines < 2 || major_lines < 2)
		return;
	dw = width / (major_lines - 1);
	dh = height / (major_lines - 1);
	vec3Copy(neworg,center);
	neworg[0] -= (vec_t)fmod(center[0] - origin[0],dw);
	neworg[1] -= (vec_t)fmod(center[1] - origin[1],dh);
#ifdef	AA_ENABLED
	glLineWidth(1.5f);
#endif	/* AA_ENABLED */
	draw_xOy_grid(neworg,width,height,minor_lines);
#ifdef	AA_ENABLED
	glLineWidth(3);
#endif	/* AA_ENABLED */
	draw_xOy_grid(neworg,width,height,major_lines);
}

V_LOCAL void begin_overlay(int char_width, int char_height)
{
	glMatrixMode(GL_PROJECTION);
	glPushMatrix();
	glLoadIdentity();
	glOrtho(0,char_width,0,char_height,-1,1);
	glMatrixMode(GL_MODELVIEW);
	glPushMatrix();
	glLoadIdentity();
	VGL_TRANSLATE(0,char_height-1,0);
}

V_LOCAL void end_overlay(void)
{
	glMatrixMode(GL_PROJECTION);
	glPopMatrix();
	glMatrixMode(GL_MODELVIEW);
	glPopMatrix();
}

vec_t scale_base = 3;
vec_t view_scale, scale_exp, scale_mnt;
int seed = -1;

V_LOCAL void draw_status()
{
	begin_overlay(40,30);
	vglPrintf(
		"Seed %d\n"
		"Time %4.3g\n"
		"View scale %g * %1.3g * %g\n",
		seed,
		simulation_time,
		view_scale,scale_mnt,drawstate.zoom);
	end_overlay();
}

V_LOCAL void draw_reset_state(void)
{
	drawstate.rotx = drawstate.rotz = 0;
	drawstate.zoom = 1;
	drawstate.vdx = drawstate.vdy = drawstate.vdz = 0;
	drawstate.inner_grid = drawstate.outer_grid = true;
	drawstate.ortho = true;
	drawstate.lod = 1;
}

V_LOCAL void mnt_and_exp(vec_t *mnt_ret,vec_t *exp_ret,vec_t x,vec_t base)
{
	vec_t sg;
	double ex, y;
	if(fabs(x) < 1e-50)
	{
		/* hack */
		if(mnt_ret)
			*mnt_ret = 1;
		if(exp_ret)
			*exp_ret = -50;
		return;
	}
	else
		if(x < 0)
		{
			sg = -1;
			x = -x;
		}
		else
			sg = 1;
	y = log10(x) / log10(base);
	ex = floor(y);
	if(exp_ret)
		*exp_ret = (vec_t)ex;
	if(mnt_ret)
		*mnt_ret = sg * (vec_t)pow(base,y - ex);
}

V_LOCAL void draw(vec_t pos_vel[])
{
	int i;
	vec3_t mins, maxs, origin, center;
	glClear(GL_COLOR_BUFFER_BIT);
	glPushMatrix();
	
    glRotatef(drawstate.rotx,1,0,0);
    glRotatef(drawstate.rotz,0,0,1);
	/*
	 * Setup view transform from current positions
	 */
	bboxClear(mins,maxs);
	for(i=0;i<NUM_OBJECTS;i++)
		bboxAddPoint(mins,maxs,&pos_vel[i * 6]);
	vec3Zero(origin);
	vec3Sub(center,maxs,mins);
	view_scale = vec3Abs(center);
	if(view_scale != 0.0)
	{
		view_scale = 1.0 / view_scale;
	}
	else
		view_scale = 1e5;
	/*
	 * Round view_scale to nearest power of 'scale_base'
	 */
	mnt_and_exp(&scale_mnt,&scale_exp,view_scale,scale_base);
	view_scale = pow(scale_base,scale_exp);
	vec3Add(center,mins,maxs);
	center[2] = 0;
	vec3Scale(center,center,0.5f);
	VGL_SCALE(scale_mnt,scale_mnt,scale_mnt);
	VGL_TRANSLATE(-center[0]+drawstate.vdx,-center[1]+drawstate.vdy,-center[2]+drawstate.vdz);
	glScalef(drawstate.zoom,drawstate.zoom,drawstate.zoom);
	/*
	 * Draw coordsys-aligned grid on xOy plane
	 */
	vec3Set(origin,0,0,0);
	if(drawstate.outer_grid)
	{
		glColor4f(.5f,1.0f,0,0.3f);
		draw_2_xOy_grids(origin,center,8,8,3,9);
	}
	if(drawstate.inner_grid)
	{
		glColor4f(.5f,1.0f,0,0.6f);
		draw_2_xOy_grids(origin,center,2,2,3,9);
	}
	VGL_SCALE(view_scale,view_scale,view_scale);
	/* Origin */
	glColor4f(1,1,1,0.7f);
	glPointSize(6);
	glBegin(GL_POINTS);
		VGL_VERTEX3V(origin);
	glEnd();
	/* Particles */
	if(drawstate.lod > 0)
	{
#ifdef	AA_ENABLED
		glLineWidth(1.5f);
#endif	/* AA_ENABLED */
		for(i=0;i<NUM_OBJECTS;i++)
			draw_particle(drawstate.lod,&pos_vel[i*6],obj[i].radius,obj[i].radius+r0);
	}
	else
	{
		glColor4f(1,0,1,0.8f);
		glBegin(GL_POINTS);
		for(i=0;i<NUM_OBJECTS;i++)
			VGL_VERTEX3V(&pos_vel[i * 6]);
		glEnd();
	}
	/* Ticks from particles to zero plane */
	if(drawstate.inner_grid || drawstate.outer_grid)
	{
#ifdef	AA_ENABLED
		glLineWidth(1.7f);
#endif	/* AA_ENABLED */
		glColor4f(1,0,0,0.8f);
		glBegin(GL_LINES);
			for(i=0;i<NUM_OBJECTS;i++)
			{
				vec_p p = &pos_vel[i * 6];
				vec3_t p2;
				vec3Set(p2,p[0],p[1],0);
				VGL_VERTEX3V(p);
				VGL_VERTEX3V(p2);
			}
		glEnd();
	}
	/* Draw status */
#ifdef	AA_ENABLED
	glLineWidth(1.3f);
#endif	/* AA_ENABLED */
	glColor4f(1,0.95f,0.93f,0.8f);
	draw_status();
	/* Done */
	glPopMatrix();
	vglSwapBuffers();
}

void ogl_setup_view(u32_t view_width,u32_t view_height,vboolean ortho,vboolean clip)
{
	GLfloat aspect;
	if(view_width && view_height)
	{
		glViewport(0,0,view_width,view_height);
		if(clip)
		{
			glScissor(0,0,view_width,view_height);
			glEnable(GL_SCISSOR_TEST);
		}
		else
			glDisable(GL_SCISSOR_TEST);
		aspect = (GLfloat)view_width / (GLfloat)view_height;
	}
	else
		aspect =  4.0f / 3.0f;
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	if(ortho)
		glOrtho(-aspect,aspect,-1,1,1,50);
	else
		glFrustum(-aspect,aspect,-1,1,1.5f,50);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	glTranslatef(0,0,-4);
}

vboolean ogl_setup(u32_t view_width,u32_t view_height,vboolean fullscreen)
{
	char *title=
		"Initial value problem for LJ-particles (RK4 + Runge corrector)";
	if(fullscreen)
		vglCreateFullscreenWindow(title,view_width,view_height,0,0);
	else
		vglCreateWindow(title,0,0,view_width,view_height);
	if(vglGetCurrentWindow()==LIBV_INVALID_HANDLE)
		return false;
	vglLoadDefaultFont();
	draw_reset_state();
	ogl_setup_view(view_width,view_height,drawstate.ortho,false);
	/* Current matrix mode is MODELVIEW */
#ifdef	AA_ENABLED
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
	glEnable(GL_LINE_SMOOTH);
	glEnable(GL_POINT_SMOOTH);
#endif	/* AA_ENABLED */
	return true;
}

void ogl_shutdown(void)
{
	vglDestroyWindow(vglGetCurrentWindow());
}

void draw_handle_keydown(u32_t keycode)
{
	switch(keycode)
	{
		case key_up:
			drawstate.rotx += 10;
			break;
		case key_down:
			drawstate.rotx -= 10;
			break;
		case key_left:
			drawstate.rotz += 10;
			break;
		case key_right:
			drawstate.rotz -= 10;
			break;
		case key_numpad_4:
			drawstate.vdx += 1;
			break;
		case key_numpad_6:
			drawstate.vdx -= 1;
			break;
		case key_numpad_2:
			drawstate.vdy += 1;
			break;
		case key_numpad_8:
			drawstate.vdy -= 1;
			break;
		case key_numpad_7:
			drawstate.vdz += 1;
			break;
		case key_numpad_9:
			drawstate.vdz -= 1;
			break;
		case key_grey_minus:
			drawstate.zoom /= (GLfloat)1.1;
			break;
		case key_grey_plus:
			drawstate.zoom *= (GLfloat)1.1;
			break;
		case key_I:
			drawstate.inner_grid = !drawstate.inner_grid;
			break;
		case key_O:
			drawstate.outer_grid = !drawstate.outer_grid;
			break;
		case key_D:
			drawstate.lod = (drawstate.lod + 1) % 3;
			break;
		case key_P:
			drawstate.ortho = !drawstate.ortho;
			ogl_setup_view(0,0,drawstate.ortho,false);
			break;
		case key_space:
			draw_reset_state();
			ogl_setup_view(0,0,drawstate.ortho,false);
			break;
		case key_numpad_5:
			drawstate.zoom *= 0.1f;
			break;
		default:
			break;
	}
}

void draw_handle_keyup(u32_t keycode)
{
	switch(keycode)
	{
		case key_numpad_5:
			drawstate.zoom *= 10;
			break;
		default:
			break;
	}
}

/*
 * -----------------------------------------------------------------------
 * Part 5: UI
 */
vboolean ui_handle_event(input_record_t *in)
{
	switch(in->event)
	{
		case ev_key_down:
			if(in->first_code == key_escape)
				return false;
			draw_handle_keydown(in->first_code);
			break;
		case ev_key_up:
			draw_handle_keyup(in->first_code);
			break;
		default:
			break;
	}
	return true;
}

void ui_help(void)
{
	conPuts(
		"Interactive mode keys:\n"
		"Escape - exit;\n"
		"Cursor keys - rotation;\n"
		"Numpad keys (4/6;2/8;7/9) - shift in object space;\n"
		"Numpad +/- - zoom in/out;\n"
		"Numpad 5 - temporary zoom out;\n"
		"'I','O' - toggle inner/outer grids;\n"
		"'D' - toggle particle level of detail.\n");
}

#define solver_max_dt	3e-2

u32_t last_draw_time;

vboolean V_CALL monitor(void *param,vec_t t,vec_t x[])
{
	u32_t time, wait_time;
	input_record_t in;
	simulation_time = t;
	while(vglPending())
	{
		vglNextEvent(&in);
		if(!ui_handle_event(&in))
			return false;
	}
	time = timeTickCount();
	draw(x);
	wait_time = time - last_draw_time;
	if(wait_time < 30)
		timeDelay(30 - wait_time);
	last_draw_time = time;
	return true;
}

/*
 * main()
 */

vboolean nodraw = false;
vboolean noburn = false;
vec_t	timelimit = 1e6;
u32_t	rtlimit = (3600 * 1000);	/* 1 hour */
vec_t FTOL = 1e-4;

void ui_main_loop(void)
{
	vhandle_t solver;

	ui_help();
	
	solver = odeCreateSolver(6 * NUM_OBJECTS,problem_right_side,true);
	odeSetMonitor(solver,monitor,NULL,0.001,true);
	last_draw_time = timeTickCount();
	odeIntegrate(solver,pos_vel,0,10000,FTOL,1e-4,1e-15);
	odeDestroySolver(solver);
}


void cmd_help(void)
{
	puts(
		"Command-line switches:\n"
		"nodraw\n"
		"noburn\n"
		"rtlimit <int arg>\n"
		"ftol <float arg>\n"
		"timelimit <float arg>\n"
		"rectangular\n"
		"sinh\n"
		"strafemul <float arg>\n"
		"eta <float arg>\n"
		"u0 <float arg>\n"
		"out <string arg>\n"
		"seed <int arg>\n"
		"uhole <float arg>\n"
		"r0 <floar arg>\n");
}

char *outfile = NULL;

vboolean cmd_parsecommandline(int argc,char *argv[])
{
	int i;
	for(i=1;i<argc;i++)
	{
		char *cmd = argv[i];
		if(!strcmp(cmd,"nodraw"))
		{
			nodraw = true;
			puts("nodraw: true");
		}
		else if(!strcmp(cmd,"out"))
		{
			outfile = argv[++i];
			printf("out: '%s'\n",outfile);
		}
		else if(!strcmp(cmd,"seed"))
		{
			seed = atoi(argv[++i]);
			printf("seed: '%d'\n",seed);
		}
		else if(!strcmp(cmd,"noburn"))
		{
			noburn = true;
			puts("noburn: true");
		}
		else if(!strcmp(cmd,"timelimit"))
		{
			timelimit = atof(argv[++i]);
			printf("timelimit %g\n",timelimit);
		}
		else if(!strcmp(cmd,"fps"))
		{
			FPS = atoi(argv[++i]);
			printf("FPS %u\n",FPS);
		}
		else if(!strcmp(cmd,"rtlimit"))
		{
			rtlimit = 1000 * atoi(argv[++i]);
			printf("rtlimit %g\n",timelimit);
		}
		else if(!strcmp(cmd,"rectangular"))
		{
			EXTRA_force = strafe_rectangular;
			puts("rectangular strafe");
		}
		else if(!strcmp(cmd,"sinh"))
		{
			EXTRA_force = strafe_sinh;
			puts("sinh strafe");
		}
		else if(!strcmp(cmd,"strafemul"))
		{
			potential_barrier_height = atof(argv[++i]);
			printf("potential_barrier_height %g\n",potential_barrier_height);
		}
		else if(!strcmp(cmd,"eta"))
		{
			ETA = atof(argv[++i]);
			printf("ETA %g\n",ETA);
		}
		else if(!strcmp(cmd,"ftol"))
		{
			FTOL = atof(argv[++i]);
			printf("FTOL %g\n",FTOL);
		}
		else if(!strcmp(cmd,"u0"))
		{
			U0 = atof(argv[++i]);
			printf("U0 %g\n",U0);
		}
		else if(!strcmp(cmd,"uhole"))
		{
			potential_barrier_width = atof(argv[++i]);
			printf("potential_barrier_width %g\n",potential_barrier_width);
		}
		else if(!strcmp(cmd,"r0"))
		{
			r0 = atof(argv[++i]);
			printf("r0 %g\n",r0);
		}
		else
		{
			printf("Unrecognized command: %s\n",cmd);
			return false;
		}
	}
	return true;
}

vhandle_t out;

void begin_framedesc()
{
	u32_t i;
	out = fileOpenWrite(outfile ? outfile : "out.psy");
	filePrintf(out,"title \"(noname)\"\n");
	filePrintf(out,"particles %d\n",NUM_OBJECTS);
	filePrintf(out,"fps\t%u\n",FPS);
	for(i=0;i<NUM_OBJECTS;i++)
	{
		filePrintf(out,"particle %u\n{\n",i);
		filePrintf(out,"\tghost\t0\t%g\n\tghost\t1\t%g\n",
			obj[i].radius,obj[i].radius+r0);
		filePrintf(out,"}\n");
	}
}

u32_t total_frames = 0;

void end_framedesc()
{
	filePrintf(out,"frames %u\n",total_frames);
	fileClose(out);
}

vboolean noflush = false;

void save_frame(vec_t t,vec_t x[],u32_t framenum)
{
	int i;
	if(total_frames < framenum)
		total_frames = framenum;
	simulation_time = t;
	filePrintf(out,"frame %u\n{\n",framenum);
	for(i=0;i<NUM_OBJECTS;i++)
	{
		vec_t *p = &x[i * 6];
		vec_t *v = &x[i * 6 + 3];
		filePrintf(out,"\t{%g,%g,%g},{%g,%g,%g}%s\n",
			p[0],p[1],p[2],
			v[0],v[1],v[2],
			(i < (NUM_OBJECTS-1)) ? "," : "");
		if(!noflush)
			fileControl(out,pctl_flush,NULL);
	}
	filePrintf(out,"}\n");
}

u32_t fr = 0;
u32_t rt;
u32_t starttime;

vboolean V_CALL monitor2(void *param,vec_t t,vec_t x[])
{
	rt = timeTickCount() - starttime;
	conPrintf("Frame %u ready: time %4.3g, realtime %u\n",fr,simulation_time,rt);
	save_frame(t,x,fr++);
	
	if(rt >= rtlimit)
	{
		conPuts("rtlimit hit: exiting.\n");
		return false;
	}
	
	return true;
}

void produce_frames(void)
{
	vhandle_t solver;
	
	solver = odeCreateSolver(6 * NUM_OBJECTS,problem_right_side,true);
	odeSetMonitor(solver,monitor2,NULL,(vec_t)1.0 / FPS,true);
	conPrintf("Timelimit %g\n",timelimit);
	conPrintf("RTlimit %u\n",rtlimit);
	starttime = timeTickCount();
	odeIntegrate(solver,pos_vel,0,rtlimit,FTOL,1e-4,1e-15);
	odeDestroySolver(solver);
}

int main(int argc,char *argv[])
{
	logStartConsoleLog();
	logUpperLevel(LOG_TRACE);

	cmd_help();

	if(!cmd_parsecommandline(argc,argv))
		return -1;

	ljutil_init();
	problem_initial_value();
	
	/* fsInit(); */
	if(nodraw)
	{
		begin_framedesc();
		produce_frames();
		end_framedesc();
	}
	else
	{
		if(!ogl_setup(640,480,false))
			return -1;
		ui_main_loop();
		ogl_shutdown();
	}
	logStopConsoleLog();
	return 0;
}

