/* $Id: nurbscrv.c,v 1.2 2000/11/02 00:28:54 mholst Exp $ */

/*
 * Mesa 3-D graphics library
 * Version:  2.0
 * Copyright (C) 1995-1996  Brian Paul
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free
 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/*
 * NURBS implementation written by Bogdan Sikorski (bogdan@cira.it)
 * See README2 for more info.
 */


#include <math.h>
#include <stdlib.h>
#include "nurbs.h"


static int
get_curve_dim(GLenum type)
{
	switch(type)
	{
		case GL_MAP1_VERTEX_3:			return 3;
		case GL_MAP1_VERTEX_4:			return 4;
		case GL_MAP1_INDEX:				return 1;
		case GL_MAP1_COLOR_4:			return 4;
		case GL_MAP1_NORMAL:			return 3;
		case GL_MAP1_TEXTURE_COORD_1:	return 1;
		case GL_MAP1_TEXTURE_COORD_2:	return 2;
		case GL_MAP1_TEXTURE_COORD_3:	return 3;
		case GL_MAP1_TEXTURE_COORD_4:	return 4;
                default:  abort();  /* TODO: is this OK? */
	}
        return 0; /*never get here*/
}

static GLenum
test_nurbs_curve(GLUnurbsObj *nobj, curve_attribs *attribs)
{
	GLenum err;
	GLint tmp_int;

	if(attribs->order < 0)
	{
		call_user_error(nobj,GLU_INVALID_VALUE);
		return GLU_ERROR;
	}
	glGetIntegerv(GL_MAX_EVAL_ORDER,&tmp_int);
	if(attribs->order > tmp_int || attribs->order < 2)
	{
		call_user_error(nobj,GLU_NURBS_ERROR1);
		return GLU_ERROR;
	}
	if(attribs->knot_count < attribs->order +2)
	{
		call_user_error(nobj,GLU_NURBS_ERROR2);
		return GLU_ERROR;
	}
	if(attribs->stride < 0)
	{
		call_user_error(nobj,GLU_NURBS_ERROR34);
		return GLU_ERROR;
	}
	if(attribs->knot==NULL || attribs->ctrlarray==NULL)
	{
		call_user_error(nobj,GLU_NURBS_ERROR36);
		return GLU_ERROR;
	}
	if((err=test_knot(attribs->knot_count,attribs->knot,attribs->order))
		!=GLU_NO_ERROR)
	{
		call_user_error(nobj,err);
		return GLU_ERROR;
	}
	return GLU_NO_ERROR;
}

static GLenum
test_nurbs_curves(GLUnurbsObj *nobj)
{
	/* test the geometric data */
	if(test_nurbs_curve(nobj,&(nobj->curve.geom))!=GLU_NO_ERROR)
		return GLU_ERROR;
	/* now test the attributive data */
	/* color */
	if(nobj->curve.color.type!=GLU_INVALID_ENUM)
		if(test_nurbs_curve(nobj,&(nobj->curve.color))!=GLU_NO_ERROR)
			return GLU_ERROR;
	/* normal */
	if(nobj->curve.normal.type!=GLU_INVALID_ENUM)
		if(test_nurbs_curve(nobj,&(nobj->curve.normal))!=GLU_NO_ERROR)
			return GLU_ERROR;
	/* texture */
	if(nobj->curve.texture.type!=GLU_INVALID_ENUM)
		if(test_nurbs_curve(nobj,&(nobj->curve.texture))!=GLU_NO_ERROR)
			return GLU_ERROR;
	return GLU_NO_ERROR;
}

/* prepare the knot information structures */
static GLenum
fill_knot_structures(GLUnurbsObj *nobj,knot_str_type *geom_knot,
	knot_str_type *color_knot, knot_str_type *normal_knot,
	knot_str_type *texture_knot)
{
	GLint order;
	GLfloat *knot;
	GLint nknots;
	GLint t_min,t_max;

	geom_knot->unified_knot=NULL;
	knot=geom_knot->knot=nobj->curve.geom.knot;
	nknots=geom_knot->nknots=nobj->curve.geom.knot_count;
	order=geom_knot->order=nobj->curve.geom.order;
	geom_knot->delta_nknots=0;
	t_min=geom_knot->t_min=order-1;
	t_max=geom_knot->t_max=nknots-order;
	if(fabs(knot[t_min]-knot[t_max])<EPSILON)
	{
		call_user_error(nobj,GLU_NURBS_ERROR3);
		return GLU_ERROR;
	}
	if(fabs(knot[0]-knot[t_min])<EPSILON)
	{
		/* knot open at beggining */
		geom_knot->open_at_begin=GL_TRUE;
	}
	else
		geom_knot->open_at_begin=GL_FALSE;
	if(fabs(knot[t_max]-knot[nknots-1])<EPSILON)
	{
		/* knot open at end */
		geom_knot->open_at_end=GL_TRUE;
	}
	else
		geom_knot->open_at_end=GL_FALSE;
	if(nobj->curve.color.type!=GLU_INVALID_ENUM)
	{
		color_knot->unified_knot=(GLfloat *)1;
		knot=color_knot->knot=nobj->curve.color.knot;
		nknots=color_knot->nknots=nobj->curve.color.knot_count;
		order=color_knot->order=nobj->curve.color.order;
		color_knot->delta_nknots=0;
		t_min=color_knot->t_min=order-1;
		t_max=color_knot->t_max=nknots-order;
		if(fabs(knot[t_min]-knot[t_max])<EPSILON)
		{
			call_user_error(nobj,GLU_NURBS_ERROR3);
			return GLU_ERROR;
		}
		if(fabs(knot[0]-knot[t_min])<EPSILON)
		{
			/* knot open at beggining */
			color_knot->open_at_begin=GL_TRUE;
		}
		else
			color_knot->open_at_begin=GL_FALSE;
		if(fabs(knot[t_max]-knot[nknots-1])<EPSILON)
		{
			/* knot open at end */
			color_knot->open_at_end=GL_TRUE;
		}
		else
			color_knot->open_at_end=GL_FALSE;
	}
	else
		color_knot->unified_knot=NULL;
	if(nobj->curve.normal.type!=GLU_INVALID_ENUM)
	{
		normal_knot->unified_knot=(GLfloat *)1;
		knot=normal_knot->knot=nobj->curve.normal.knot;
		nknots=normal_knot->nknots=nobj->curve.normal.knot_count;
		order=normal_knot->order=nobj->curve.normal.order;
		normal_knot->delta_nknots=0;
		t_min=normal_knot->t_min=order-1;
		t_max=normal_knot->t_max=nknots-order;
		if(fabs(knot[t_min]-knot[t_max])<EPSILON)
		{
			call_user_error(nobj,GLU_NURBS_ERROR3);
			return GLU_ERROR;
		}
		if(fabs(knot[0]-knot[t_min])<EPSILON)
		{
			/* knot open at beggining */
			normal_knot->open_at_begin=GL_TRUE;
		}
		else
			normal_knot->open_at_begin=GL_FALSE;
		if(fabs(knot[t_max]-knot[nknots-1])<EPSILON)
		{
			/* knot open at end */
			normal_knot->open_at_end=GL_TRUE;
		}
		else
			normal_knot->open_at_end=GL_FALSE;
	}
	else
		normal_knot->unified_knot=NULL;
	if(nobj->curve.texture.type!=GLU_INVALID_ENUM)
	{
		texture_knot->unified_knot=(GLfloat *)1;
		knot=texture_knot->knot=nobj->curve.texture.knot;
		nknots=texture_knot->nknots=nobj->curve.texture.knot_count;
		order=texture_knot->order=nobj->curve.texture.order;
		texture_knot->delta_nknots=0;
		t_min=texture_knot->t_min=order-1;
		t_max=texture_knot->t_max=nknots-order;
		if(fabs(knot[t_min]-knot[t_max])<EPSILON)
		{
			call_user_error(nobj,GLU_NURBS_ERROR3);
			return GLU_ERROR;
		}
		if(fabs(knot[0]-knot[t_min])<EPSILON)
		{
			/* knot open at beggining */
			texture_knot->open_at_begin=GL_TRUE;
		}
		else
			texture_knot->open_at_begin=GL_FALSE;
		if(fabs(knot[t_max]-knot[nknots-1])<EPSILON)
		{
			/* knot open at end */
			texture_knot->open_at_end=GL_TRUE;
		}
		else
			texture_knot->open_at_end=GL_FALSE;
	}
	else
		texture_knot->unified_knot=NULL;
	return GLU_NO_ERROR;
}

/* covert the NURBS curve into a series of adjacent Bezier curves */
static GLenum
convert_curve(knot_str_type *the_knot, curve_attribs *attrib,
	GLfloat **new_ctrl,GLint *ncontrol)
{
	GLenum err;

	if((err=explode_knot(the_knot))!=GLU_NO_ERROR)
	{
		if(the_knot->unified_knot)
		{
			free(the_knot->unified_knot);
			the_knot->unified_knot=NULL;
		}
		return err;
	}
	if(the_knot->unified_knot)
	{
		free(the_knot->unified_knot);
		the_knot->unified_knot=NULL;
	}
	if((err=calc_alphas(the_knot))!=GLU_NO_ERROR)
	{
		free(the_knot->new_knot);
		return err;
	}
	free(the_knot->new_knot);
	if((err=calc_new_ctrl_pts(attrib->ctrlarray,attrib->stride,the_knot,
							attrib->dim,new_ctrl,ncontrol))
		!=GLU_NO_ERROR)
	{
		free(the_knot->alpha);
		return err;
	}
	free(the_knot->alpha);
	return GLU_NO_ERROR;
}

/* covert curves - geometry and possible attribute ones into equivalent */
/* sequence of adjacent Bezier curves */
static GLenum
convert_curves(GLUnurbsObj *nobj, GLfloat **new_geom_ctrl,
	GLint *ncontrol, GLfloat **new_color_ctrl, GLfloat **new_normal_ctrl,
	GLfloat **new_texture_ctrl)
{
	knot_str_type	geom_knot,color_knot,normal_knot,texture_knot;
	GLint			junk;
	GLenum			err;

	*new_color_ctrl=*new_normal_ctrl=*new_texture_ctrl=NULL;

	if(fill_knot_structures(nobj,&geom_knot,&color_knot,&normal_knot,
			&texture_knot)!=GLU_NO_ERROR)
		return GLU_ERROR;

	/* unify knots - all knots should have the same number of working */
	/* ranges */
	if((err=select_knot_working_range(nobj,&geom_knot,&color_knot,&normal_knot,
				&texture_knot))!=GLU_NO_ERROR)
	{
		return err;
	}
	/* convert the geometry curve */
	nobj->curve.geom.dim=get_curve_dim(nobj->curve.geom.type);
	if((err=convert_curve(&geom_knot,&(nobj->curve.geom),new_geom_ctrl,
						ncontrol))!=GLU_NO_ERROR)
	{
		free_unified_knots(&geom_knot,&color_knot,&normal_knot,&texture_knot);
		call_user_error(nobj,err);
		return err;
	}
	/* if additional attributive curves are given convert them as well */
	if(color_knot.unified_knot)
	{
		nobj->curve.color.dim=get_curve_dim(nobj->curve.color.type);
		if((err=convert_curve(&color_knot,&(nobj->curve.color),
			new_color_ctrl,&junk))!=GLU_NO_ERROR)
		{
			free_unified_knots(&geom_knot,&color_knot,&normal_knot,&texture_knot);
			free(*new_geom_ctrl);
			call_user_error(nobj,err);
			return err;
		}
	}
	if(normal_knot.unified_knot)
	{
		nobj->curve.normal.dim=get_curve_dim(nobj->curve.normal.type);
		if((err=convert_curve(&normal_knot,&(nobj->curve.normal),
			new_normal_ctrl,&junk))!=GLU_NO_ERROR)
		{
			free_unified_knots(&geom_knot,&color_knot,&normal_knot,&texture_knot);
			free(*new_geom_ctrl);
			if(*new_color_ctrl)
				free(*new_color_ctrl);
			call_user_error(nobj,err);
			return err;
		}
	}
	if(texture_knot.unified_knot)
	{
		nobj->curve.texture.dim=get_curve_dim(nobj->curve.texture.type);
		if((err=convert_curve(&texture_knot,&(nobj->curve.texture),
			new_texture_ctrl,&junk))!=GLU_NO_ERROR)
		{
			free_unified_knots(&geom_knot,&color_knot,&normal_knot,&texture_knot);
			free(*new_geom_ctrl);
			if(*new_color_ctrl)
				free(*new_color_ctrl);
			if(*new_normal_ctrl)
				free(*new_normal_ctrl);
			call_user_error(nobj,err);
			return err;
		}
	}
	return GLU_NO_ERROR;
}

/* main NURBS curve procedure */
void do_nurbs_curve( GLUnurbsObj *nobj)
{
	GLint geom_order,color_order=0,normal_order=0,texture_order=0;
	GLenum geom_type;
	GLint n_ctrl;
	GLfloat *new_geom_ctrl,*new_color_ctrl,*new_normal_ctrl,*new_texture_ctrl;
	GLfloat *geom_ctrl,*color_ctrl,*normal_ctrl,*texture_ctrl;
	GLint *factors;
	GLint i,j;
	GLint geom_dim,color_dim=0,normal_dim=0,texture_dim=0;

	/* test the user supplied data */
	if(test_nurbs_curves(nobj)!=GLU_NO_ERROR)
		return;

	if(convert_curves(nobj,&new_geom_ctrl,&n_ctrl,&new_color_ctrl,
					&new_normal_ctrl,&new_texture_ctrl)!=GLU_NO_ERROR)
		return;

	geom_order=nobj->curve.geom.order;
	geom_type=nobj->curve.geom.type;
	geom_dim=nobj->curve.geom.dim;

	if(glu_do_sampling_2D(nobj,new_geom_ctrl,n_ctrl,geom_order,geom_dim,
						&factors)
		!=GLU_NO_ERROR)
	{
		free(new_geom_ctrl);
		if(new_color_ctrl)
			free(new_color_ctrl);
		if(new_normal_ctrl)
			free(new_normal_ctrl);
		if(new_texture_ctrl)
			free(new_texture_ctrl);
		return;
	}
	glEnable(geom_type);
	if(new_color_ctrl)
	{
		glEnable(nobj->curve.color.type);
		color_dim=nobj->curve.color.dim;
		color_ctrl=new_color_ctrl;
		color_order=nobj->curve.color.order;
	}
	if(new_normal_ctrl)
	{
		glEnable(nobj->curve.normal.type);
		normal_dim=nobj->curve.normal.dim;
		normal_ctrl=new_normal_ctrl;
		normal_order=nobj->curve.normal.order;
	}
	if(new_texture_ctrl)
	{
		glEnable(nobj->curve.texture.type);
		texture_dim=nobj->curve.texture.dim;
		texture_ctrl=new_texture_ctrl;
		texture_order=nobj->curve.texture.order;
	}
	for(i=0 , j=0, geom_ctrl=new_geom_ctrl;
		i<n_ctrl;
		i+=geom_order , j++ , geom_ctrl+=geom_order*geom_dim)
	{
		if(fine_culling_test_2D(nobj,geom_ctrl,geom_order,geom_dim,geom_dim))
		{
			color_ctrl+=color_order*color_dim;
			normal_ctrl+=normal_order*normal_dim;
			texture_ctrl+=texture_order*texture_dim;
			continue;
		}
		glMap1f(geom_type, 0.0, 1.0, geom_dim, geom_order, geom_ctrl);
		if(new_color_ctrl)
		{
			glMap1f(nobj->curve.color.type, 0.0, 1.0, color_dim,
				color_order,color_ctrl);
			color_ctrl+=color_order*color_dim;
		}
		if(new_normal_ctrl)
		{
			glMap1f(nobj->curve.normal.type, 0.0, 1.0, normal_dim,
				normal_order,normal_ctrl);
			normal_ctrl+=normal_order*normal_dim;
		}
		if(new_texture_ctrl)
		{
			glMap1f(nobj->curve.texture.type, 0.0, 1.0, texture_dim,
				texture_order,texture_ctrl);
			texture_ctrl+=texture_order*texture_dim;
		}
		glMapGrid1f(factors[j],0.0,1.0);
		glEvalMesh1(GL_LINE,0,factors[j]);
	}
	free(new_geom_ctrl);
	free(factors);
	if(new_color_ctrl)
		free(new_color_ctrl);
	if(new_normal_ctrl)
		free(new_normal_ctrl);
	if(new_texture_ctrl)
		free(new_texture_ctrl);
}


