#include <v_lib.h>

/*
 * Another demonstation of using spline interpolators.
 * This program generates coarse rectangular mesh and
 * than refines all sides of the mesh.
 */

#define	AA_ENABLED

/*
 * Number of subdivisions of coarse mesh
 */
#define	MESH_DIVISIONS			5

/*
 * Number of subdivisions of refined mesh sides
 */
#define	NUM_SUBDIVISIONS		256

V_LOCAL vec3_t mesh_min,mesh_max;	/* bounding box */

V_LOCAL vec3_t mesh_vertices[MESH_DIVISIONS][MESH_DIVISIONS]; /* mesh */

/*
 * compute_z() must fill third field in 'in_out' with some
 * 'good-looking' value.
 */
V_LOCAL void compute_z(vec3_t in_out)
{
	/* in_out[2] = 1 - (in_out[0] * in_out[0] + in_out[1] * in_out[1]); */
	in_out[2] = (in_out[0] * in_out[0] - in_out[1] * in_out[1]);
}

/*
 * Generates coarse mesh
 */
V_LOCAL void init_mesh_vertices()
{
	int i,j;
	vec_t x,y,dx,dy;
	bboxClear(mesh_min,mesh_max);
	dx = dy = (vec_t)2 / (MESH_DIVISIONS-1);
	y = -1;
	for(i=0;i<MESH_DIVISIONS;i++)
	{
		x = -1;
		for(j=0;j<MESH_DIVISIONS;j++)
		{
			mesh_vertices[j][i][0] = x;
			mesh_vertices[j][i][1] = y;
			compute_z(mesh_vertices[j][i]);
			bboxAddPoint(mesh_min,mesh_max,mesh_vertices[j][i]);
			x += dx;
		}
		y += dy;
	}
}

/*
 * Draws wireframe box with given corners
 */
V_LOCAL void draw_bounding_box(vec3_t mins,vec3_t maxs)
{
	glBegin(GL_LINE_LOOP);
		glVertex3d(maxs[0],maxs[1],mins[2]);
		glVertex3d(mins[0],maxs[1],mins[2]);
		glVertex3d(mins[0],mins[1],mins[2]);
		glVertex3d(maxs[0],mins[1],mins[2]);
	glEnd(); /* GL_LINE_LOOP */
	glBegin(GL_LINE_LOOP);
		glVertex3d(maxs[0],maxs[1],maxs[2]);
		glVertex3d(mins[0],maxs[1],maxs[2]);
		glVertex3d(mins[0],mins[1],maxs[2]);
		glVertex3d(maxs[0],mins[1],maxs[2]);
	glEnd(); /* GL_LINE_LOOP */
	glBegin(GL_LINES);
		glVertex3d(maxs[0],maxs[1],mins[2]);
		glVertex3d(maxs[0],maxs[1],maxs[2]);
		glVertex3d(mins[0],maxs[1],mins[2]);
		glVertex3d(mins[0],maxs[1],maxs[2]);
		glVertex3d(mins[0],mins[1],mins[2]);
		glVertex3d(mins[0],mins[1],maxs[2]);
		glVertex3d(maxs[0],mins[1],mins[2]);
		glVertex3d(maxs[0],mins[1],maxs[2]);
	glEnd(); /* GL_LINES */
}

/*
 * Draws vertices together with bounding box
 */
V_LOCAL void draw_mesh_vertices()
{
	int i,j;

	glColor4f(.8f,.8f,.8f,0.7f);
	draw_bounding_box(mesh_min,mesh_max);

	glColor4f(1,1,1,0.8f);
	glPointSize(9);
	glBegin(GL_POINTS);
	for(i=0;i<MESH_DIVISIONS;i++)
		for(j=0;j<MESH_DIVISIONS;j++)
			VGL_VERTEX3V(mesh_vertices[i][j]);
	glEnd(); /* GL_POINTS */
}

/*
 * Interpolators for every direction
 */
vhandle_t	side_splines[2][MESH_DIVISIONS];

/*
 * Creates interpolators on the base of coarse mesh
 */
V_LOCAL void init_splines()
{
	int i,j;
	vector_t *vx, *vy;
	vec_t x, dx;
	vx = vecAlloc(MESH_DIVISIONS);
	vy = vecAlloc(MESH_DIVISIONS);
	/* Setup X vector */
	x = 0;
	dx = (vec_t)(NUM_SUBDIVISIONS - 1) / (vec_t)(MESH_DIVISIONS - 1);
	for(i=0;i<MESH_DIVISIONS;i++,x+=dx)
		vx->elements[i] = x;
	/* x direction: y from z coord */
	for(i=0;i<MESH_DIVISIONS;i++)
	{
		for(j=0;j<MESH_DIVISIONS;j++)
			vy->elements[j] = mesh_vertices[j][i][2];
		side_splines[0][i] = cspCreate(vx,vy);
	}
	/* y direction: y from x coord */
	for(i=0;i<MESH_DIVISIONS;i++)
	{
		for(j=0;j<MESH_DIVISIONS;j++)
			vy->elements[j] = mesh_vertices[i][j][2];
		side_splines[1][i] = cspCreate(vx,vy);
	}
	memFree(vx);
	memFree(vy);
}

/*
 * Destroys all interpolators
 */
V_LOCAL void destroy_splines()
{
	int i;
	for(i=0;i<MESH_DIVISIONS;i++)
	{
		cspDestroy(side_splines[0][i]);
		cspDestroy(side_splines[1][i]);
	}
}

/*
 * Draws refined linestrips
 */
V_LOCAL void draw_splines()
{
	int i,j;
	vec_t x,y,z,dx,dy;
	glPointSize(1);
	glColor3f(.7f,.8f,0);
	/* x direction */
	y = -1;
	dy = (vec_t)2 / (MESH_DIVISIONS - 1);
	dx = (vec_t)2 / (NUM_SUBDIVISIONS - 1);
	for(i=0;i<MESH_DIVISIONS;i++)
	{
		x = -1;
		glBegin(GL_LINE_STRIP);
		for(j=0;j<NUM_SUBDIVISIONS;j++)
		{
			z = cspInterpolate(side_splines[0][i],(vec_t)j);
			glVertex3d(x,y,z);
			x += dx;
		}
		glEnd(); /* GL_LINE_STRIP */
		y += dy;
	}
	/* y direction */
	x = -1;
	dx = (vec_t)2 / (MESH_DIVISIONS - 1);
	dy = (vec_t)2 / (NUM_SUBDIVISIONS - 1);
	for(i=0;i<MESH_DIVISIONS;i++)
	{
		y = -1;
		glBegin(GL_LINE_STRIP);
		for(j=0;j<NUM_SUBDIVISIONS;j++)
		{
			z = cspInterpolate(side_splines[1][i],(vec_t)j);
			glVertex3d(x,y,z);
			y += dy;
		}
		glEnd(); /* GL_LINE_STRIP */
		x += dx;
	}
}

/*
 * Display list to hold all scene
 */
GLuint scene = 0;

/*
 * Creates display list
 */
V_LOCAL void precompile_scene()
{
	scene = glGenLists(1);
	glNewList(scene,GL_COMPILE);
	draw_mesh_vertices();
	draw_splines();
	glEndList();
}

/*
 * Performs all initialization, frees unnecessary data
 */
V_LOCAL void initialize()
{
	init_mesh_vertices();
	init_splines();
	precompile_scene();
	destroy_splines();

	// glClearColor(0,0.5f,0.5f,0);
	glClearColor(0,0,0,0);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glFrustum(-1,1,-1,1,2,50);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	glTranslatef(0,0,-5);

}

/*
 * At exit we must only free GL display list
 */
V_LOCAL void shutdown()
{
	if(glIsList(scene))
		glDeleteLists(scene,1);
}

/*
 * Rotation angles for scene animation
 */
vec_t alpha = 0, beta = 0;

/*
 * Function fo call each frame
 */
V_LOCAL void draw_scene()
{
	glClear(GL_COLOR_BUFFER_BIT);

	glPushMatrix();
	glRotated(alpha,1,0,0);
	glRotated(beta,0,1,0);
	glCallList(scene);
	glPopMatrix();

	vglSwap();

	alpha = (vec_t)fmod(alpha + 1,360);
	beta = (vec_t)fmod(beta + 2,360);
}

/*
 * Event loop
 */
V_LOCAL void main_loop()
{
	vboolean done = false;
	ui_event_t ev;
	while(!done)
	{
		while(uiPending())
		{
			uiNextEvent(&ev);
			switch(ev.type)
			{
				case ui_key_event:
					done = true;
					break;
				default:
					break;
			}
		}
		draw_scene();
		timeDelay(10);
	}
}

int main()
{
	logStartConsoleLog();
	logUpperLevel(LOG_TRACE);
	assert(vglInit("Spline test (press any key for exit)",
		0,0,512,512,
		false,false,
		false,false,false));
#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 */
	initialize();
	main_loop();
	shutdown();
	vglDestroy();
	memPrintStats();
	return 0;
}
