/* nurbsutl.c */

/*
 * Mesa 3-D graphics library
 * Version:  1.2
 * Copyright (C) 1995  Brian Paul  (brianp@ssec.wisc.edu)
 *
 * 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.
 */


/*
$Id: nurbsutl.c,v 1.5 1995/11/03 14:15:13 brianp Exp $

$Log: nurbsutl.c,v $
 * Revision 1.5  1995/11/03  14:15:13  brianp
 * Bogdan's November 3, 1995 updates
 *
 * Revision 1.4  1995/09/20  18:25:57  brianp
 * removed Bogdan's old email address
 *
 * Revision 1.3  1995/08/04  13:09:59  brianp
 * include gluP.h to define NULL, just in case
 *
 * Revision 1.2  1995/07/28  21:37:30  brianp
 * updates from Bogdan on July 28
 *
 * Revision 1.1  1995/07/28  14:45:10  brianp
 * Initial revision
 *
 */


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


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

GLenum
test_knot(GLint nknots, GLfloat *knot, GLint order)
{
	GLsizei i;
	GLint knot_mult;
	GLfloat tmp_knot;

	tmp_knot=knot[0];
	knot_mult=1;
	for(i=1;i<nknots;i++)
	{
		if(knot[i] < tmp_knot)
			return GLU_NURBS_ERROR4;
		if(fabs(tmp_knot-knot[i]) > EPSILON)
		{
			if(knot_mult>order)
				return GLU_NURBS_ERROR5;
			knot_mult=1;
			tmp_knot=knot[i];
		}
		else
			++knot_mult;
	}
	return GLU_NO_ERROR;
}

GLenum
explode_knot(knot_str_type *the_knot)
{
	GLfloat *knot,*new_knot;
	GLint nknots,n_new_knots=0;
	GLint t_min,t_max;
	GLint ord;
	GLboolean knot_open_at_begin=GL_FALSE,knot_open_at_end=GL_FALSE;
	GLsizei i,j,k;
	GLfloat tmp_float;

	if(the_knot->unified_knot)
	{
		knot=the_knot->unified_knot;
		nknots=the_knot->unified_nknots;
	}
	else
	{
		knot=the_knot->knot;
		nknots=the_knot->nknots;
	}
	ord=the_knot->order;
	t_min=the_knot->t_min;
	t_max=the_knot->t_max;

	if(the_knot->open_at_begin)
	{
		/* knot open at beggining */
		knot_open_at_begin=GL_TRUE;
		++t_min;
	}
	if(the_knot->open_at_end)
	{
		/* knot open at end */
		knot_open_at_end=GL_TRUE;
		--t_max;
	}
	for(i=t_min;i<=t_max;)
	{
		tmp_float=knot[i];
		for(j=0;j<ord && (i+j)<=t_max;j++)
			if(fabs(tmp_float-knot[i+j])>EPSILON)
				break;
		n_new_knots+=ord-j;
		i+=j;
	}
	/* alloc space for new_knot */
	if((new_knot=(GLfloat *)malloc(sizeof(GLfloat)*(nknots+n_new_knots)))==NULL)
	{
		return GLU_OUT_OF_MEMORY;
	}
	/* fill in new knot */
	for(j=0;j<t_min;j++)
		new_knot[j]=knot[j];
	for(i=j;i<=t_max;i++)
	{
		tmp_float=knot[i];
		for(k=0;k<ord;k++)
		{
			new_knot[j++]=knot[i];
			if(tmp_float==knot[i+1])
				i++;
		}
	}
	for(i=t_max+1;i<(int)nknots;i++)
		new_knot[j++]=knot[i];
	/* fill in the knot structure */
	the_knot->new_knot=new_knot;
	the_knot->delta_nknots+=n_new_knots;
	return GLU_NO_ERROR;
}

GLenum
calc_alphas(knot_str_type *the_knot)
{
	GLfloat tmp_float;
	int i,j,k,m,n;
	int order;
	GLfloat *alpha,*alpha_new,*tmp_alpha;
	GLfloat denom;
	GLfloat *knot,*new_knot;


	knot=the_knot->knot;
	order=the_knot->order;
	new_knot=the_knot->new_knot;
	n=the_knot->nknots-the_knot->order;
	m=n+the_knot->delta_nknots;
	if((alpha=(GLfloat *)malloc(sizeof(GLfloat)*n*m))==NULL)
	{
		return GLU_OUT_OF_MEMORY;
	}
	if((alpha_new=(GLfloat *)malloc(sizeof(GLfloat)*n*m))==NULL)
	{
		free(alpha);
		return GLU_OUT_OF_MEMORY;
	}
	for(j=0;j<m;j++)
	{
		for(i=0;i<n;i++)
		{
			if((knot[i] <= new_knot[j]) && (new_knot[j] < knot[i+1]))
				tmp_float=1.0;
			else
				tmp_float=0.0;
			alpha[i+j*n]=tmp_float;
		}
	}
	for(k=1;k<order;k++)
	{
		for(j=0;j<m;j++)
			for(i=0;i<n;i++)
			{
				denom=knot[i+k]-knot[i];
				if(fabs(denom)<EPSILON)
					tmp_float=0.0;
				else
					tmp_float=(new_knot[j+k]-knot[i])/denom*
						alpha[i+j*n];
				denom=knot[i+k+1]-knot[i+1];
				if(fabs(denom)>EPSILON)
					tmp_float+=(knot[i+k+1]-new_knot[j+k])/denom*
						alpha[(i+1)+j*n];
				alpha_new[i+j*n]=tmp_float;
			}
		tmp_alpha=alpha_new;
		alpha_new=alpha;
		alpha=tmp_alpha;
	}
	the_knot->alpha=alpha;
	free(alpha_new);
	return GLU_NO_ERROR;
}

GLenum
calc_new_ctrl_pts(GLfloat *ctrl,GLint stride,knot_str_type *the_knot,
	GLint dim,GLfloat **new_ctrl,GLint *ncontrol)
{
	GLsizei i,j,k,l,m,n;
	GLsizei index1,index2;
	GLfloat *alpha;
	GLfloat *new_knot;

	new_knot=the_knot->new_knot;
	n=the_knot->nknots-the_knot->order;
	m=n+the_knot->delta_nknots;
	alpha=the_knot->alpha;
	if(the_knot->open_at_begin==GL_TRUE)
	{
		k=0;
	}
	else
	{
		k=the_knot->order-1;
		m-=k;
	}
	/* is knot open at end? */
	if(the_knot->open_at_end==GL_FALSE)
		m-=the_knot->order-1;
	/* allocate space for new control points */
	if((*new_ctrl=(GLfloat *)malloc(sizeof(GLfloat)*dim*m))==NULL)
	{
		return GLU_OUT_OF_MEMORY;
	}
	for(j=0;j<m;j++)
	{
		for(l=0;l<dim;l++)
			(*new_ctrl)[j*dim+l]=0.0;
		for(i=0;i<n;i++)
		{
			index1=i+(j+k)*n;
			index2=i*stride;
			for(l=0;l<dim;l++)
				(*new_ctrl)[j*dim+l]+=alpha[index1]*ctrl[index2+l];
		}
	}
	*ncontrol=(GLint)m;
	return GLU_NO_ERROR;
}

static GLint
calc_factor(GLfloat *pts,GLint order,GLint indx,GLint stride,GLfloat tolerance,
	GLint dim)
{
	GLdouble model[16],proj[16];
	GLint viewport[4];
	GLdouble x,y,z,w,winx1,winy1,winz,winx2,winy2;
	GLint i;
	GLdouble len,dx,dy;

	glGetDoublev(GL_MODELVIEW_MATRIX,model);
	glGetDoublev(GL_PROJECTION_MATRIX,proj);
	glGetIntegerv(GL_VIEWPORT,viewport);
	if(dim==3)
	{
		x=(GLdouble)pts[indx];
		y=(GLdouble)pts[indx+1];
		z=(GLdouble)pts[indx+2];
		gluProject(x,y,z,model,proj,viewport,&winx1,&winy1,&winz);
		len=0.0;
		for(i=1;i<order;i++)
		{
			x=(GLdouble)pts[indx+i*stride];
			y=(GLdouble)pts[indx+i*stride+1];
			z=(GLdouble)pts[indx+i*stride+2];
			if(gluProject(x,y,z,model,proj,viewport,&winx2,&winy2,&winz))
			{
				dx=winx2-winx1;
				dy=winy2-winy1;
				len+=sqrt(dx*dx+dy*dy);
			}
			winx1=winx2; winy1=winy2;
		}
	}
	else
	{
		w=(GLdouble)pts[indx+3];
		x=(GLdouble)pts[indx]/w;
		y=(GLdouble)pts[indx+1]/w;
		z=(GLdouble)pts[indx+2]/w;
		gluProject(x,y,z,model,proj,viewport,&winx1,&winy1,&winz);
		len=0.0;
		for(i=1;i<order;i++)
		{
			w=(GLdouble)pts[indx+i*stride+3];
			x=(GLdouble)pts[indx+i*stride]/w;
			y=(GLdouble)pts[indx+i*stride+1]/w;
			z=(GLdouble)pts[indx+i*stride+2]/w;
			if(gluProject(x,y,z,model,proj,viewport,&winx2,&winy2,&winz))
			{
				dx=winx2-winx1;
				dy=winy2-winy1;
				len+=sqrt(dx*dx+dy*dy);
			}
			winx1=winx2; winy1=winy2;
		}
	}
	len /= tolerance;
	return (GLint)len;
}

static GLenum
calc_sampling_3D(GLfloat *ctrl, GLint ucnt, GLint vcnt, GLint uorder, GLint vorder,
	GLfloat tolerance, GLint dim, GLint **ufactors, GLint **vfactors)
{
	GLint		ufactor_cnt,vfactor_cnt;
	GLint		tmp_factor,max_factor;
	GLint		offset1,offset2;
	GLint		i,j;

	ufactor_cnt=ucnt/uorder;
	vfactor_cnt=vcnt/vorder;
	if((*ufactors=(GLint *)malloc(sizeof(GLint)*ufactor_cnt))==NULL)
	{
		return GLU_OUT_OF_MEMORY;
	}
	if((*vfactors=(GLint *)malloc(sizeof(GLint)*vfactor_cnt))==NULL)
	{
		free(ufactors);
		return GLU_OUT_OF_MEMORY;
	}
	offset1=vorder*dim;
	offset2=vcnt*dim;
	for(j=0;j<vfactor_cnt;j++)
	{
		for(i=0 , max_factor=1;i<ucnt;i++)
		{
			tmp_factor=calc_factor(ctrl,vorder,j*offset1+i*offset2,
				dim,tolerance,dim);
			if(tmp_factor>max_factor)
				max_factor=tmp_factor;
		}
		(*vfactors)[j]=max_factor;
	}
	offset1=offset2;
	offset2*=uorder;
	for(j=0;j<ufactor_cnt;j++)
	{
		for(i=0 , max_factor=1;i<vcnt;i++)
		{
			tmp_factor=calc_factor(ctrl,uorder,j*offset2+i*dim,
				offset1,tolerance,dim);
			if(tmp_factor>max_factor)
				max_factor=tmp_factor;
		}
		(*ufactors)[j]=max_factor;
	}
	return GL_NO_ERROR;
}

static GLenum
calc_sampling_2D(GLfloat *ctrl, GLint cnt, GLint order,
	GLfloat tolerance, GLint dim, GLint **factors)
{
	GLint		factor_cnt;
	GLint		tmp_factor;
	GLint		offset;
	GLint		i;

	factor_cnt=cnt/order;
	if((*factors=(GLint *)malloc(sizeof(GLint)*factor_cnt))==NULL)
	{
		return GLU_OUT_OF_MEMORY;
	}
	offset=order*dim;
	for(i=0;i<factor_cnt;i++)
	{
		tmp_factor=calc_factor(ctrl,order,i*offset,dim,tolerance,dim);
		if(tmp_factor == 0)
			(*factors)[i]=1;
		else
			(*factors)[i]=tmp_factor;
	}
	return GL_NO_ERROR;
}

static void
set_sampling_and_culling( GLUnurbsObj *nobj )
{
	if(nobj->auto_load_matrix==GL_FALSE)
	{
		GLint i;
		GLfloat m[4];

		glPushAttrib(GL_VIEWPORT_BIT | GL_TRANSFORM_BIT);
		for(i=0;i<4;i++)
			m[i]=nobj->sampling_matrices.viewport[i];
		glViewport(m[0],m[1],m[2],m[3]);
		glMatrixMode(GL_PROJECTION);
		glPushMatrix();
		glLoadMatrixf(nobj->sampling_matrices.proj);
		glMatrixMode(GL_MODELVIEW);
		glPushMatrix();
		glLoadMatrixf(nobj->sampling_matrices.model);
	}
}

static void
revert_sampling_and_culling( GLUnurbsObj *nobj )
{
	if(nobj->auto_load_matrix==GL_FALSE)
	{
		glMatrixMode(GL_MODELVIEW);
		glPopMatrix();
		glMatrixMode(GL_PROJECTION);
		glPopMatrix();
		glPopAttrib();
	}
}

GLenum
glu_do_sampling_3D( GLUnurbsObj *nobj, GLfloat *ctrl, GLint scnt, GLint tcnt,
	GLint **sfactors, GLint **tfactors)
{
	GLint  dim;
	GLenum err;

	*sfactors=NULL;
	*tfactors=NULL;
	dim=nobj->surface.geom.dim;
	set_sampling_and_culling(nobj);
	if((err=calc_sampling_3D(ctrl,scnt,tcnt,nobj->surface.geom.sorder,
		nobj->surface.geom.torder,nobj->sampling_tolerance,dim,
		sfactors,tfactors))==GLU_ERROR)
	{
		revert_sampling_and_culling(nobj);
		call_user_error(nobj,err);
		return GLU_ERROR;
	}
	revert_sampling_and_culling(nobj);
	return GLU_NO_ERROR;
}

GLenum
glu_do_sampling_2D( GLUnurbsObj *nobj, GLfloat *ctrl, GLint cnt, GLint order,
	GLint dim, GLint **factors)
{
	GLenum err;

	*factors=NULL;
	set_sampling_and_culling(nobj);
	if((err=calc_sampling_2D(ctrl,cnt,order,nobj->sampling_tolerance,dim,
		factors))==GLU_ERROR)
	{
		revert_sampling_and_culling(nobj);
		call_user_error(nobj,err);
		return GLU_ERROR;
	}
	revert_sampling_and_culling(nobj);
	return GLU_NO_ERROR;
}

/* TODO - i don't like this culling - this one just tests if at least one */
/* ctrl point lies within the viewport . Also the point_in_viewport() */
/* should be included in the fnctions for efficiency reasons */

static GLboolean
point_in_viewport(GLfloat *pt, GLint dim)
{
	GLdouble model[16],proj[16];
	GLint viewport[4];
	GLdouble x,y,z,w,winx,winy,winz;

	glGetDoublev(GL_MODELVIEW_MATRIX,model);
	glGetDoublev(GL_PROJECTION_MATRIX,proj);
	glGetIntegerv(GL_VIEWPORT,viewport);
	if(dim==3)
	{
		x=(GLdouble)pt[0];
		y=(GLdouble)pt[1];
		z=(GLdouble)pt[2];
		gluProject(x,y,z,model,proj,viewport,&winx,&winy,&winz);
	}
	else
	{
		w=(GLdouble)pt[3];
		x=(GLdouble)pt[0]/w;
		y=(GLdouble)pt[1]/w;
		z=(GLdouble)pt[2]/w;
		gluProject(x,y,z,model,proj,viewport,&winx,&winy,&winz);
	}
	if((GLint)winx >= viewport[0] && (GLint)winx < viewport[2] &&
			(GLint)winy >= viewport[1] && (GLint)winy < viewport[3])
		return GL_TRUE;
	return GL_FALSE;
}

GLboolean
fine_culling_test_3D(GLUnurbsObj *nobj,GLfloat *pts,GLint s_cnt,GLint t_cnt,
	GLint s_stride,GLint t_stride, GLint dim)
{
	GLint 	i,j;

	if(nobj->culling==GL_FALSE)
		return GL_FALSE;
	set_sampling_and_culling(nobj);
	
	if(dim==3)
	{
		for(i=0;i<s_cnt;i++)
			for(j=0;j<t_cnt;j++)
				if(point_in_viewport(pts+i*s_stride+j*t_stride,dim))
				{
					revert_sampling_and_culling(nobj);
					return GL_FALSE;
				}
	}
	else
	{
		for(i=0;i<s_cnt;i++)
			for(j=0;j<t_cnt;j++)
				if(point_in_viewport(pts+i*s_stride+j*t_stride,dim))
				{
					revert_sampling_and_culling(nobj);
					return GL_FALSE;
				}
	}
	revert_sampling_and_culling(nobj);
	return GL_TRUE;
}

/*GLboolean
fine_culling_test_3D(GLUnurbsObj *nobj,GLfloat *pts,GLint s_cnt,GLint t_cnt,
	GLint s_stride,GLint t_stride, GLint dim)
{
	GLint		visible_cnt;
	GLfloat		feedback_buffer[5];
	GLsizei		buffer_size;
	GLint 		i,j;

	if(nobj->culling==GL_FALSE)
		return GL_FALSE;
	buffer_size=5;
	set_sampling_and_culling(nobj);
	
	glFeedbackBuffer(buffer_size,GL_2D,feedback_buffer);
	glRenderMode(GL_FEEDBACK);
	if(dim==3)
	{
		for(i=0;i<s_cnt;i++)
		{
			glBegin(GL_LINE_LOOP);
			for(j=0;j<t_cnt;j++)
				glVertex3fv(pts+i*s_stride+j*t_stride);
			glEnd();
		}
		for(j=0;j<t_cnt;j++)
		{
			glBegin(GL_LINE_LOOP);
			for(i=0;i<s_cnt;i++)
				glVertex3fv(pts+i*s_stride+j*t_stride);
			glEnd();
		}
	}
	else
	{
		for(i=0;i<s_cnt;i++)
		{
			glBegin(GL_LINE_LOOP);
			for(j=0;j<t_cnt;j++)
				glVertex4fv(pts+i*s_stride+j*t_stride);
			glEnd();
		}
		for(j=0;j<t_cnt;j++)
		{
			glBegin(GL_LINE_LOOP);
			for(i=0;i<s_cnt;i++)
				glVertex4fv(pts+i*s_stride+j*t_stride);
			glEnd();
		}
	}
	visible_cnt=glRenderMode(GL_RENDER);

	revert_sampling_and_culling(nobj);
	return (GLboolean)(visible_cnt==0);
}*/

GLboolean
fine_culling_test_2D(GLUnurbsObj *nobj,GLfloat *pts,GLint cnt,
	GLint stride, GLint dim)
{
	GLint 	i;

	if(nobj->culling==GL_FALSE)
		return GL_FALSE;
	set_sampling_and_culling(nobj);
	
	if(dim==3)
	{
		for(i=0;i<cnt;i++)
			if(point_in_viewport(pts+i*stride,dim))
			{
				revert_sampling_and_culling(nobj);
				return GL_FALSE;
			}
	}
	else
	{
		for(i=0;i<cnt;i++)
			if(point_in_viewport(pts+i*stride,dim))
			{
				revert_sampling_and_culling(nobj);
				return GL_FALSE;
			}
	}
	revert_sampling_and_culling(nobj);
	return GL_TRUE;
}

/*GLboolean
fine_culling_test_2D(GLUnurbsObj *nobj,GLfloat *pts,GLint cnt,
	GLint stride, GLint dim)
{
	GLint		visible_cnt;
	GLfloat		feedback_buffer[5];
	GLsizei		buffer_size;
	GLint 		i;

	if(nobj->culling==GL_FALSE)
		return GL_FALSE;
	buffer_size=5;
	set_sampling_and_culling(nobj);
	
	glFeedbackBuffer(buffer_size,GL_2D,feedback_buffer);
	glRenderMode(GL_FEEDBACK);
	glBegin(GL_LINE_LOOP);
	if(dim==3)
	{
		for(i=0;i<cnt;i++)
			glVertex3fv(pts+i*stride);
	}
	else
	{
		for(i=0;i<cnt;i++)
			glVertex4fv(pts+i*stride);
	}
	glEnd();
	visible_cnt=glRenderMode(GL_RENDER);

	revert_sampling_and_culling(nobj);
	return (GLboolean)(visible_cnt==0);
}*/

