#include <v_lib.h>

#pragma warning(disable:4244)

/*
 * Another demonstation of using spline interpolators.
 * This program generates 3D parametric curve based on user-supplied
 * coordinates.
 */

/*
 * Number of subdivisions between each point
 */
#define	CURVE_SUBDIVISIONS				16

V_LOCAL vec3_t curve_min, curve_max;	/* bounding box */

/*
 * Helper procedures: skips to given token, returns 'false' on EOF/error
 */
V_LOCAL vboolean skip_to_token(scanner_t scanner,char *token)
{
	if(tokLast(scanner) && !strcmp(tokLast(scanner),token))
		return true;
	while(tokNext(scanner))
		if(!strcmp(tokLast(scanner),token))
			return true;
	return false;
}

V_LOCAL vboolean skip_to_token_in_braces(scanner_t scanner,char *token)
{
	if(tokLast(scanner) && !strcmp(tokLast(scanner),token))
		return true;
	while(tokNext(scanner))
		if(!strcmp(tokLast(scanner),token))
			return true;
		else
			if(!strcmp(tokLast(scanner),"}"))
				break;
	return false;
}

/*
 * Read in coordinates of shape's "node_name" line 'line_number'.
 * Note: "node_name" == NULL means first encountered shape;
 * 'line_number' == 0 means first line in shape.
 */
V_LOCAL vboolean
dsmax_read_curve_vertices(vector_p coord_vectors[3],vboolean *close_flag,
	char *dsmax_ase_file,char *node_name,int line_number)
{
	u32_t knot, num_knots;
	vector_p x,y,z;
    vec3_t point;
	int num_lines;
	scanner_t ase;
    u32_t closed;
	char shape_name[256];
	ase = tokParseFile(dsmax_ase_file,"{}");
	if(ase == LIBV_INVALID_HANDLE)
		return false;
	/*
	 * Check either or not passed file is valid 3DS MAX ASE file
	 */
	if(tokNext(ase) && strcmp(tokLast(ase),"*3DSMAX_ASCIIEXPORT"))
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,"read_curve_vertices(%s): "
			"isn't valid 3DS MAX ASE file.\n",dsmax_ase_file);
#endif	/* CHECKED_BUILD */
		tokDestroy(ase);
		return false;
	}
	/*
	 * Search for given node
	 */
	do
	{
		if(!skip_to_token(ase,"*SHAPEOBJECT"))
		{
#ifdef	CHECKED_BUILD
			conlPrintf(LOG_ERROR,"read_curve_vertices(%s): "
				"no more shapes.\n",dsmax_ase_file);
#endif	/* CHECKED_BUILD */
			tokDestroy(ase);
			return false;
		}
		skip_to_token(ase,"*NODE_NAME");
		V_ASSERT(tokNext(ase)); /* node name */
	} while(node_name && strcmp(node_name,tokLast(ase)));
	strCopy(shape_name,tokLast(ase),256);
#ifdef	CHECKED_BUILD
	conlPrintf(LOG_INFO,"read_curve_vertices(%s): found shape '%s'.\n",
		dsmax_ase_file,tokLast(ase));
#endif	/* CHECKED_BUILD */
	/*
	 * Search for given line
	 */
	V_ASSERT(skip_to_token(ase,"*SHAPE_LINECOUNT"));
	V_ASSERT(tokNext(ase));
	num_lines = atoi(tokLast(ase));
	if(line_number >= num_lines)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,"read_curve_vertices(%s):  too few lines "
			"in shape '%s': expected at least %d, found %d.\n",
			dsmax_ase_file,shape_name,line_number+1,num_lines);
#endif	/* CHECKED_BUILD */                                               
		tokDestroy(ase);
		return false;
	}
	for(;;)
	{
		if(!skip_to_token_in_braces(ase,"*SHAPE_LINE"))
		{
#ifdef	CHECKED_BUILD
			conlPrintf(LOG_ERROR,"read_curve_vertices(%s): "
				"line %d not found in shape '%s'.\n",
				dsmax_ase_file,line_number+1,shape_name);
#endif	/* CHECKED_BUILD */                                               
			tokDestroy(ase);
			return false;
		}
		if(atoi(tokNext(ase)) != line_number)
		{
			/* skip this line */
			V_ASSERT(skip_to_token(ase,"}"));
			continue;
		}
        break;
	}
	V_ASSERT(tokNext(ase)); /* open brace */
	V_ASSERT(tokNext(ase));
	if(!strcmp(tokLast(ase),"*SHAPE_CLOSED"))
    	closed = 1;
	else
    	closed = 0;
	if(close_flag != NULL)
		*close_flag = closed == 1;
	V_ASSERT(skip_to_token_in_braces(ase,"*SHAPE_VERTEXCOUNT"));
	V_ASSERT(tokNext(ase));
	num_knots = (u32_t)atoi(tokLast(ase));
	V_ASSERT(num_knots > 2);
    x = vecAlloc(num_knots+closed);
    y = vecAlloc(num_knots+closed);
    z = vecAlloc(num_knots+closed);
	/*
     * Read in coordinates
     */
	bboxClear(curve_min,curve_max);
	for(knot=0;knot<num_knots;knot++)
    {
    	if(!skip_to_token_in_braces(ase,"*SHAPE_VERTEX_KNOT"))
        {
#ifdef	CHECKED_BUILD
			conlPrintf(LOG_ERROR,"read_curve_vertices(%s):  too few knots "
				"on line %d in shape '%s': expected %d, found %d.\n",
				dsmax_ase_file,line_number,shape_name,num_knots,knot);
#endif	/* CHECKED_BUILD */
			memFree(x);
			memFree(y);
			memFree(z);
			tokDestroy(ase);
			return false;
        }
        V_ASSERT(tokNext(ase));
        V_ASSERT(knot == (u32_t)atoi(tokLast(ase)));
        V_ASSERT(tokNext(ase));
        x->elements[knot] = point[0] = (vec_t)atof(tokLast(ase));
        V_ASSERT(tokNext(ase));
        y->elements[knot] = point[1] = (vec_t)atof(tokLast(ase));
        V_ASSERT(tokNext(ase));
        z->elements[knot] = point[2] = (vec_t)atof(tokLast(ase));
		bboxAddPoint(curve_min,curve_max,point);
	}
    if(closed)
    {
    	x->elements[knot] = x->elements[0];
    	y->elements[knot] = y->elements[0];
    	z->elements[knot] = z->elements[0];
    }
    coord_vectors[0] = x;
    coord_vectors[1] = y;
    coord_vectors[2] = z;
    tokDestroy(ase);
    return true;
}

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

/*
 * Draws polyline
 */
V_LOCAL void draw_line_strip(vector_p coord_vectors[3])
{
	u32_t i;
    vector_p x = coord_vectors[0], y = coord_vectors[1], z = coord_vectors[2];
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
	glEnable(GL_LINE_SMOOTH);
    glLineWidth(2.0f);
    glBegin(GL_LINE_STRIP);
	glColor4f(1,1,1,.9f);
    for(i=0;i<x->size;i++)
    	glVertex3d(x->elements[i],y->elements[i],z->elements[i]);
	glEnd();
}

/*
 * Generates parametric representation of the curve.
 * Parameter range are [0..1] * (N-1), where N is a number of
 * points in curve (e.g. for 3-point curve range is [0..2]).
 */
V_LOCAL void
init_splines(vector_p coord_vectors[3],vhandle_t *lerp_ret)
{
	u32_t i;
	vec_t t, dt;
	vector_p t_range = vecAlloc(coord_vectors[0]->size);
    for(i=0,t=0,dt=1;i<t_range->size;i++,t+=dt)
    	t_range->elements[i] = t;
	*lerp_ret = cspNdCreate(t_range,coord_vectors,3);
	memFree(t_range);
}

#define t_min 0
#define t_max 11

/*
 * Draws refined line
 */
V_LOCAL void
draw_splines(vhandle_t curve_lerp,u32_t num_segments)
{
	int i, len;
	vec_t pt[3] = {0,0,0};
    vec_t t, dt;
    dt = (vec_t)1 / CURVE_SUBDIVISIONS;
	t = t_min;
	len = num_segments * CURVE_SUBDIVISIONS;
    glLineWidth(3);
    glColor4f(1,1,0,.5f);
	glBegin(GL_LINE_STRIP);
    for(i=0;i<=len && (t<=t_max);i++,t+=dt)
    {
    	cspNdInterpolate(curve_lerp,t,pt);
		glVertex3d(pt[0],pt[1],pt[2]);
    }
    glEnd();
}

V_LOCAL void draw_shape(vector_p coord_vectors[3],vhandle_t lerp)
{
	draw_bounding_box(curve_min,curve_max);
	draw_line_strip(coord_vectors);
	draw_splines(lerp,coord_vectors[0]->size-1);
}

/*
 * Performs all initialization
 */

vector_p coord_vectors[3];
vhandle_t lerp;

V_LOCAL vboolean initialize(char *fname)
{
	if(!dsmax_read_curve_vertices(coord_vectors,NULL,fname,NULL,0))
    	return false;
    V_ASSERT(coord_vectors[0]->size>3);
	init_splines(coord_vectors,&lerp);
	vglCreateWindow("3D curve test (press any key for exit)",0,0,512,512);
	
	return true;
}

V_LOCAL void shutdown()
{
	u32_t i;
	cspNdDestroy(lerp);
	for(i=0;i<3;i++)
		memFree(coord_vectors[i]);
	vglDestroyWindow(vglGetCurrentWindow());
}

/*
 * Scene rotation angles
 */
vec_t alpha = 0, betta = 0;

V_LOCAL void draw_scene()
{
	glClearColor(0,0.5f,0.5f,0);
	glClear(GL_COLOR_BUFFER_BIT);

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glOrtho(curve_min[0],curve_max[0],curve_min[1],curve_max[1],curve_min[2],curve_max[2]);
	
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	glScalef(.5f,.5f,.5f);

	glRotatef(alpha,1,0,0);
	glRotatef(betta,0,1,0);

    draw_shape(coord_vectors,lerp);

    vglSwapBuffers();

	alpha = (vec_t)fmod(alpha + 0.5f,360);
	betta = (vec_t)fmod(betta + 0.2f,360);
}

/*
 * Event loop
 */
V_LOCAL void main_loop()
{
	vboolean done = false;
	input_record_t in_rec;
	while(!done)
	{
		while(vglPending())
		{
			vglNextEvent(&in_rec);
			switch(in_rec.event)
			{
				case ev_key_down:
					done = true;
					break;
				default:
					break;
			}
		}
		draw_scene();
		timeDelay(30);
	}
}

vboolean FileExists(char *fname)
{
	FILE *fp = fopen(fname,"rb");
	if(!fp)
		return false;
	fclose(fp);
	return true;
}

int main(int argc,char *argv[])
{
	vboolean test_exists = FileExists("test.ase");
	logStartConsoleLog();
	logUpperLevel(LOG_TRACE);
    if(argc!=2 && !test_exists)
    {
    	puts("You must specify ASE file for input");
        fflush(stdout);
        return -1;
	}
	if(!initialize(test_exists ? "test.ase" : argv[1]))
    	return -1;
	main_loop();
	shutdown();
	memPrintStats();
	return 0;
}
