/*<html><pre>*/
#include <../source/TAL/objbase.h>

/*
 *	TODO: convert to use meshStrokeXXX APIs
 */

#define INTERNAL_SPLIT 100

/*
 *	Настройки для генерации изолиний.
 *	Не изменяйте эти параметры, если Вы не уверены в том, что делаете.
 */

#define ON_EPSILON      ((vec_t)(1e-5))
#define DIFF_EPSILON    ((vec_t)(1e-7))

/*
	========================
	triSplitPoints
	Треугольник: vlist[indices[0]] - vlist[indices[2]]
	Результат: false если пересечения нет, иначе true. В последнем случае
	две точки пересечения с плоскостью pl (возможно, совпадающие)
	помещаются в dest[0] и dest[1]
	========================
*/

vboolean	V_LOCAL
triSplitPoints(vec3_t dest[],vec3_t vlist[],itriple_t indices,plane3_p pl)
{
	const int SIDE_ON = 0;
	const int SIDE_FRONT = 1;
	const int SIDE_BACK = 2;
	int sides[3]={0,0,0};
	int counts[3]={0,0,0};
	int ind,i,i1,i2;
	vec_t dist;
	line3_t l1,l2;
	const vec_t on_epsilon=(vec_t)ON_EPSILON;
	for(i=0;i<3;i++)
	{
		dist=vec3Dot(pl->normal,vlist[indices[i]])-pl->dist;
		if(dist>on_epsilon)
		{
			sides[i]=SIDE_FRONT;
			counts[SIDE_FRONT]++;
		}
		else if(dist<(-on_epsilon))
		{
			sides[i]=SIDE_BACK;
			counts[SIDE_BACK]++;
		}
		else
		{
			sides[i]=SIDE_ON;
			counts[SIDE_ON]++;
		}
	}
	if(!counts[SIDE_ON])
	{
		/* Плоскости не касается ни одна из вершин */
		if(counts[SIDE_FRONT]<counts[SIDE_BACK]) 
			ind=SIDE_FRONT;
		else
			ind=SIDE_BACK;
		for(i=0;i<3;i++)
			if(sides[i]==ind) break;
		switch(i)
		{
			case 0: i1=1; i2=2; break;
			case 1: i1=0; i2=2; break;
			case 2: i1=0; i2=1; break;
			/* all vertices either in front, or beyond the plane */
			default: return false; 
		}
		/* compute two lines, than cross them with plane */
		line3FromPoints(&l1,vlist[indices[i]],vlist[indices[i1]]);
		line3FromPoints(&l2,vlist[indices[i]],vlist[indices[i2]]);
		vec3FromLineAndPlane(dest[0],&l1,pl);
		vec3FromLineAndPlane(dest[1],&l2,pl);
		return true;
	}
	if(counts[SIDE_ON]>1)
	{
		/* take first 2 vertices which are laying on plane */
		for(ind=i=0;i<3;i++)
		{
			if(sides[i]==SIDE_ON)
			{
				vec3Copy(dest[ind],vlist[indices[i]]);
				if(ind++==3) break;
			}
		}
		return true;
	}
	/* counts[SIDE_ON]==1
	 * take one vertex which is laying on plane
	 */
	for(i=0;i<3;i++)
		if(sides[i]==SIDE_ON)
		{
			vec3Copy(dest[0],vlist[indices[i]]);
			break;
		}
	/* find cross between plane and line which is based on other 
	 * vertices
	 */
	switch(i)
	{
		case 0: i1=1; i2=2; break;
		case 1: i1=0; i2=2; break;
		default: i1=0; i2=1; break;
	}
	if(sides[i1]==sides[i2]) /* triangle only touches the plane */
	{
		vec3Copy(dest[1],dest[0]);
		return true;
	}
	line3FromPoints(&l1,vlist[indices[i1]],vlist[indices[i2]]);
	vec3FromLineAndPlane(dest[1],&l1,pl);
	return true;
}

#ifdef  OLD_SIDE_ON
#define SIDE_ON       OLD_SIDE_ON
#define SIDE_FRONT    OLD_SIDE_FRONT
#define SIDE_BACK     OLD_SIDE_BACK
#undef  OLD_SIDE_ON
#undef  OLD_SIDE_FRONT
#undef  OLD_SIDE_BACK
#else
#undef  SIDE_ON
#undef  SIDE_FRONT
#undef  SIDE_BACK
#endif

/*
	==========================
	patIntersectWithPlane
	Пересечение триангулированной сетки с плоскостью.
	В Dest помещается указатель на распределенный блок, содержащий пары 
	точек (возможно, совпадающих) - результатов пересечений для каждого
	треугольника сетки.
	Используйте в комплексе с uuRemoveDegenerateLines.
	==========================
*/

V_LOCAL void
patIntersectWithPlane(vec3_t **Dest,int *numpairs,tripatch_p pat,
	plane3_p pl)
{
	fifo_t pf;
	vec3_t pair[2];
	int i;
	fifoInit(&pf,2*sizeof(vec3_t));
	if(!pat || !pl) 
	{
		return;
	}
	for(i=0;i<pat->numtri;i++)
	{
		if(triSplitPoints(pair,pat->vlist,pat->tlist[i],pl))
			fifoPush(&pf,pair);
	}
	if(numpairs)
		*numpairs=fifoCount(&pf);
	if(Dest)
	{
#ifdef _MSC_VER
		/* (this for ViasualC 5.0 (opt ON :); env: NT 4.0 ) */
		__asm { nop } /* Please, DON'T REMOVE THIS */
#endif
		*Dest=(vec3_t*)memAlloc(2*sizeof(vec3_t)*fifoCount(&pf));
		i=0;
		while(fifoPop(&pf,pair))
		{
			vec3Copy((*Dest)[i++],pair[0]);
			vec3Copy((*Dest)[i++],pair[1]);
		}
	}
	fifoClear(&pf);
}

/*
	===============================
	uuRemoveDegenerateLines
	Убирает из набора пары с совпадающими точками. Распределяет новый
	блок, старый может удалить самостоятельно.
	Внимание ! Текущее значение величин XXX_EPSILON обеспечивает хорошее
	"сцепление" линий в парах и плохое удаление: эти функции создавались
	для построения картины изолиний.
	===============================
*/

int	V_LOCAL
uuRemoveDegenerateLines(vec3_t **dst,vec3_t *src,int *numlin,
	vboolean keepon)
{
	int i,j,newlin;
	int numlines=*numlin;
	vec3_t cur[2],nxt[2];
	vec3_t *temp1;
	vec3_t *temp2;
	if(!src || !numlin)
	{
		return 0;
	}
	temp1=(vec3_t*)memAlloc(2*numlines*sizeof(vec3_t));
	temp2=(vec3_t*)memAlloc(2*numlines*sizeof(vec3_t));
	newlin=0;
	for(i=0;i<numlines;i++)
	{
		if(vec3IsEqualEpsilon(src[i*2],src[i*2+1],DIFF_EPSILON))
			continue;
		vec3Copy(temp1[newlin*2],src[i*2]);
		vec3Copy(temp1[newlin*2+1],src[i*2+1]);
		++newlin;
	}
	numlines=0;
	/* следует учесть, что среди линий могут быть дубликаты */
	for(i=0;i<newlin;i++)
	{
		vec3Copy(cur[0],temp1[i*2]);
		vec3Copy(cur[1],temp1[i*2+1]);
		for(j=0;j<numlines;j++)
		{
			/*	ищем cur в массиве temp2 */
			/*	Да, я знаю, что первая итерация лишняя. Я устал от этого
			 *	кода, который отлаживаю неделю.
			 */
			vec3Copy(nxt[0],temp2[j*2]);
			vec3Copy(nxt[1],temp2[j*2+1]);
			if((vec3IsEqualEpsilon(cur[0],nxt[0],DIFF_EPSILON) &&
				vec3IsEqualEpsilon(cur[1],nxt[1],DIFF_EPSILON))||
				(vec3IsEqualEpsilon(cur[1],nxt[0],DIFF_EPSILON) &&
				vec3IsEqualEpsilon(cur[0],nxt[1],DIFF_EPSILON)))
				break;
		}
		if(j<numlines) /* dupe */
			continue;
		vec3Copy(temp2[numlines*2],cur[0]);
		vec3Copy(temp2[numlines*2+1],cur[1]);
		++numlines;
	}
	newlin=numlines;
	if(dst)
	{
		*dst=(vec3_t*)memAlloc(newlin*2*sizeof(vec3_t));
		memCopy(*dst,temp2,newlin*2*sizeof(vec3_t));
	}
	memFree(temp1);
	memFree(temp2);
	if(!keepon)
		memFree(src);
	*numlin=newlin;
	return newlin;
}

/*
	===============================
	uuChainsFromLines
	Шедевр :). Этим кодом занимался два дня. Код, кстати, получился весьма страшный.
	Вроде, работает при большинстве сочетаний входных данных и параметров X_EPSILON.
	Результат работы:
		на выходе - количество цепочек
		numvert - распределенный блок содержит количества вершин в каждой цепочке
							(если цепочка замкнута, то первая точке повторяется)
		cdata   - распределенный блок содержит индексы на элементы массива lines,
							поочередно для каждой цепочки.
	==========================
*/

/* весь фокус в том, что параметр CHAIN_EPSILON должен быть адаптивным */

#define		MAX_CHAINS			256

V_LOCAL int
uuChainsFromLines(int **cdata,int **numvert,vec3_t *lines,int numlines)
{
	vec3_t  last,next;
	int     c_start,l_ind,n_ind,other_side;
	int     i, count;
	u8_t	side;
	int     next_chain=0;
	u8_t	*sides[2];
	vp_stack_t chain;
	vp_stack_t chains[MAX_CHAINS];
	vec_t abs,chain_epsilon = 1e6;
	vpsInit(&chain);
	for(i=0;i<MAX_CHAINS;i++)
		vpsInit(chains+i);
	sides[0]=(u8_t*)memAlloc(numlines * 2);
	sides[1]=sides[0] + numlines;
	memZero(sides[0],numlines * 2);
	for(count=0;count<numlines;count++)
	{
		vec3Copy(last,lines[count*2]);
		vec3Sub(last,last,lines[count*2+1]);
		abs=vec3Abs(last);
		if(abs<chain_epsilon)
			chain_epsilon=abs;
	}
	chain_epsilon /= 2;
#if 0
	conlPrintf(LOG_TRACE,
		"ppChainsFromLines(): chain_epsilon %g\n",chain_epsilon);
#endif	/* 0 */
	
	for(;;)
	{
		for(count=0;count<numlines;count++)
			if(!(sides[0][count] + sides[1][count]))
				break;
		if(count==numlines || next_chain == MAX_CHAINS)  /* Конец */
		{
			int total_vert=0;
			if(numvert) 
			{
				*numvert=(int*)memAlloc(next_chain * sizeof(int));
				/* Количества вершин в цепочке == количество линий / 2 + 1
				*/
				for(count=0;count<next_chain;count++)
				{
					(*numvert)[count]=1 + (vpsCount(&chains[count]) >> 1);
					total_vert+=(*numvert)[count];
				}
			}
			if(cdata)
			{
				u32_t l;
				int c;
				int *nextp;
				*cdata=(int*)memAlloc(total_vert * sizeof(int));
				nextp=*cdata;
				for(c=0;c<next_chain;c++)
				{
					/*	Копируем начало, вторую вершину, 
					 *	дальше - через вершину. */
					*nextp++=(int)vpsGet(&chains[c],0);
					for(l=1;l<vpsCount(&chains[c]);l+=2)
						*nextp++=(int)vpsGet(&chains[c],l);
				}
			}
#if 0
			conlPrintf(LOG_TRACE,"%d chains\n",next_chain);
			for(count=0;count<next_chain;count++)
			{
				u32_t i;
				conlPrintf(LOG_TRACE,"chain %d:\n",count);
				for(i=0;i<vpsCount(&chains[count]);i++)
				{
					conlPrintf(LOG_TRACE,"%d: line(%d), side(%d), ( %g, %g, %g )\n",
						vpsGet(&chains[count],i),
						(u32_t)vpsGet(&chains[count],i) >> 1,
						(u32_t)vpsGet(&chains[count],i) % 2,
						lines[(u32_t)vpsGet(&chains[count],i)][0],
						lines[(u32_t)vpsGet(&chains[count],i)][1],
						lines[(u32_t)vpsGet(&chains[count],i)][2]);
				}
				conlPrintf(LOG_TRACE,"=========================\n");
			}
			conlPuts(LOG_TRACE,"'sides' state:\n");
			for(i=0;i<numlines;i++)
			{
				conlPrintf(LOG_TRACE,"line(%d): sides (%s - %s)\n",i,
					sides[0][i] ? "connected" : "memFree",
					sides[1][i] ? "connected" : "memFree" );
			}
#endif	/* 0 */
			memFree(sides[0]);
			for(i=0;i<MAX_CHAINS;i++)
					vpsClear(chains+i);
			vpsClear(&chain);
			return next_chain;
		}
		c_start=count;
		other_side = 0;
		side=0;
		l_ind=(c_start << 1) + ((side + 1) & 1);
#if	0
		conlPrintf(LOG_TRACE,"starting chain %d: c_start %d, l_ind %d\n",
			next_chain,c_start,l_ind);
#endif	/* 0 */
		vec3Copy(last,lines[l_ind]);
		vpsPush(&chain,(void_p)((c_start << 1) + side));
		vpsPush(&chain,(void_p)l_ind);
c_start_found:
		for(count=0;count<numlines;)
		{
			n_ind=(count<<1)+side;
			vec3Copy(next,lines[n_ind]);
			if((vec3IsEqualEpsilon(last,next,chain_epsilon) && !sides[side][count]) && l_ind != n_ind)
			{
#if	0
				conlPrintf(LOG_TRACE,"next at count %d, connecting %d to %d\n",
					count,vpsGet(&chain,vpsCount(&chain)-1),n_ind); 
#endif	/* 0 */
				vpsPush(&chain,(void_p)n_ind);
				sides[side][count]=1;
				sides[l_ind & 1][l_ind >> 1]=1;
				l_ind = (count << 1) + ((side + 1) & 1);
				vpsPush(&chain,(void_p)l_ind);
				vec3Copy(last,lines[l_ind]);
				count=0;
				continue;
			}
			side=(u8_t)((side+1) & (u8_t)1);
			n_ind=(count<<1)+side;
			vec3Copy(next,lines[n_ind]);
			if((vec3IsEqualEpsilon(last,next,chain_epsilon) && !sides[side][count]) && l_ind != n_ind)
			{
#if	0
				conlPrintf(LOG_TRACE,"reverse at count %d, connecting %d to %d\n",
					count,(int)vpsGet(&chain,vpsCount(&chain)-1),n_ind); 
#endif	/* 0 */
				vpsPush(&chain,(void_p)n_ind);
				sides[side][count]=1;
				sides[l_ind & 1][l_ind >> 1]=1;
				l_ind = (count << 1) + ((side + 1) & 1);
				vpsPush(&chain,(void_p)l_ind);
				vec3Copy(last,lines[l_ind]);
				count=0;
				continue;
			}
			side=(u8_t)((side+1) & (u8_t)1);
			count++;
		}
		if((vpsCount(&chain)>2) && 
			vpsGet(&chain,0)==vpsGet(&chain,vpsCount(&chain)-2) && 
			vpsGet(&chain,1)==vpsGet(&chain,vpsCount(&chain)-1) )
		{
			void_p stub;
#if 0
			/* bugbug - remove last line in case of lineloop */
			conlPuts(LOG_TRACE,"-- line loop --\n");
			conlPrintf(LOG_TRACE,"removing %d & %d from loop\n",
				vpsGet(&chain,vpsCount(&chain)-2),
				vpsGet(&chain,vpsCount(&chain)-1));
#endif	/* 0 */
			vpsPop(&chain,&stub);
			vpsPop(&chain,&stub);
		}
		else
		if(other_side==0)
		{
			/*
			если цепочка не замкнута, то достраиваем ее с другой стороны
			*/
			vp_stack_t traversed;
			void_p v;
			vpsInit(&traversed);
			while(vpsPop(&chain,&v))
				vpsPush(&traversed,v);
			vpsConcat(&chain,&traversed);
			vpsClear(&traversed);
#if	0
			conlPrintf(LOG_TRACE,
				"-- connecting to %d\n",vpsGet(&chain,vpsCount(&chain)-1));
#endif	/* 0 */
			side=(u8_t)((side + 1) & (u8_t)1);
			other_side++;
			l_ind=(int)vpsGet(&chain,vpsCount(&chain)-1);
			vec3Copy(last,lines[l_ind]);
			goto c_start_found;
		}
		else
		if(vpsCount(&chain)==2)
		{
			/* цепочка не достраивается и состит из единственной стороны -
			 * исключаем эту сторону из рассмотрения
			 */
			sides[(int)vpsGet(&chain,0) & 1][(int)vpsGet(&chain,0) >> 1]=1;
			sides[(int)vpsGet(&chain,1) & 1][(int)vpsGet(&chain,1) >> 1]=1;
		}
		vpsConcat(&chains[next_chain++],&chain);
		vpsClear(&chain);
	}
}

/*
	=====================
	uuSearchVec3 - что-то очень похожее есть в Quake.
	Ищет в массиве vecs вектор, совпадающий с v. Если находит, то просто возвращает
	индекс массива. Если не находит, то добавляет вектор v в конец массива и тоже
	возвращает индекс. Если место кончилось, то возвращает -1.
	Применяется при переходировке и индексации массивов вершин, в котором есть
	совпадающие.
	=====================
*/

V_LOCAL int
uuSearchVec3(vec3_t *vecs,vec3_t v,int *numvecs,int maxvecs)
{
	int i;
	for(i=0;i<*numvecs;i++)
		if(vec3IsEqualEpsilon(vecs[i],v,DIFF_EPSILON))
			return i;
	if(i==maxvecs)
		return -1;
	vec3Copy(vecs[i],v);
	(*numvecs)++;
	return i;
}

/*
	======================
	wireAlloc
	======================
*/

V_LOCAL wirechains_p
wireAlloc(int numvecs,int numchains,int numvert[])
{
	int           i, num_ind=0;
	wirechains_p  w;
	if(!numvecs || !numchains || !numvert) return NULL;
	for(i=0;i<numchains;i++) num_ind+=numvert[i];
	i=
		sizeof(wirechains_t)+
		numvecs*sizeof(vec3_t)+         /* vlist */
		numchains*sizeof(int)+          /* numvert */
		numchains*sizeof(int*)+         /* clist */
		num_ind*sizeof(int)+            /* ilist */
		numchains*sizeof(graph_label_t); /* llist */
	w=(wirechains_t*)memAlloc(i);
	memZero(w,i);
	w->numvert_total=numvecs;
	w->numchains=numchains;
	w->vlist=(vec3_t*)(w+1);
	w->numvert=(int*)(w->vlist+numvecs);
	w->clist=(int**)(w->numvert+numchains);
	w->ilist=(int*)(w->clist+numchains);
	w->llist=(graph_label_p)(w->ilist+num_ind);
	memCopy(w->numvert,numvert,numchains*sizeof(int));
	/*	Для доступа к содержимому массива ilist при работе с цепочками
	 *	можно применять счетчик, увеличивающийся при каждой выборке
	 *	элемента последовательно из всех цепочек. Удобнее записать адреса,
	 *	по которым в массиве ilist расположены начальные индексы цепочек.
	 */
	w->clist[0]=w->ilist;
	for(i=1;i<w->numchains;i++)
		w->clist[i]=w->clist[i-1]+w->numvert[i-1];
	return w;
}

/*
	======================
	wireFree
	======================
*/

void	V_LOCAL
wireFree(wirechains_p wire)
{
	memFree(wire); /* Copyrighted under GNU GPL :) */
}

/*
	===========================
	uuWireFromChains
	Последнее звено в цепочке преобразований, предназначенных для построения изолинии.
	На входе - набор координат, числа звеньев, индексы звеньев. Эти данные могут
	быть освобождены.
	На выходе - указатель на созданный объект wirechains_t.
	===========================
*/

static wirechains_p uuWireFromChains(vec3_t *lines,int *numvert,int *cdata,int numchains,vboolean keepon)
{
	int c,l,count;
	int numsrcind=0;
	int maxind=0;
	vec3_t *tvert;
	wirechains_p  w;
	int *tdata;
	int numvecs;
	vec_t chain_len,half_len;
	vec3_t dist;
	int numvert_min;
	if(!lines || !numvert || !cdata || !numchains) return NULL;
	/*
	 *	Находим общее количество индексов. Да, здесь дублирование операций.
	 *	Но пусть лучше будет дублирование, чем усложнение интерфейса между
	 *	функциями (тем более что этот подсчет выполняется быстро)
	 */
	for(c=0;c<numchains;c++)
	{
		numsrcind+=numvert[c];
	}
	/*  Находим верхнюю границу размера временного массива */
	for(count=0;count<numsrcind;count++)
	{
		if(maxind<cdata[count]) maxind=cdata[count];
	}
	/* В массиве tvert будут накапливаться координаты вершин */
	tvert=(vec3_t*)memAlloc((maxind+1)*sizeof(vec3_t));
	/*  В массиве tdata будут накапливаться их новые индексы */
	tdata=(int*)memAlloc(numsrcind*sizeof(int));
	numvecs=0;
	for(count=0;count<numsrcind;count++)
	{
		tdata[count]=uuSearchVec3(tvert,lines[cdata[count]],&numvecs,maxind);
	}
	/* Найдено: numvecs - число вершин. Можно распределять память. */
	w=wireAlloc(numvecs,numchains,numvert);
	if(!w)
	{
		memFree(tvert);
		memFree(tdata);
		return NULL;
	}
	memCopy(w->vlist,tvert,numvecs*sizeof(vec3_t));
	memFree(tvert);
	memCopy(w->ilist,tdata,numsrcind*sizeof(int));
	memFree(tdata);
	/* поиск центров меток */
	numvert_min = ( w->numvert_total / w->numchains ) / 10;
	if(numvert_min < 3)
		numvert_min = 3;
	else
		if(numvert_min>30)
			numvert_min=30;
	for(c=0;c<w->numchains;c++)
	{
		if(w->numvert[c]<numvert_min)
		{
			w->llist[c].flags|=LF_NOLABEL;
		}
		else
		{
			chain_len=0;
			for(l=0;l<w->numvert[c]-1;l++)
			{
				vec3Sub(dist,w->vlist[w->clist[c][l]],w->vlist[w->clist[c][l+1]]);
				chain_len+=vec3Abs(dist);
			}
			w->llist[c].elem_size=chain_len;
			half_len=chain_len/2;
			chain_len=0;
			for(l=0;chain_len<half_len;l++)
			{
				vec3Sub(dist,w->vlist[w->clist[c][l]],w->vlist[w->clist[c][l+1]]);
				chain_len+=vec3Abs(dist);
			}
			w->llist[c].flags|=LF_CENTER;
			if(l==numvert[c]-1) --l; else if(l==0) l=1;
			vec3Copy(w->llist[c].center,w->vlist[w->clist[c][l]]);
		} /* else if */
	} /* for */
	if(!keepon)
	{
		memFree(lines);
		memFree(numvert);
		memFree(cdata);
	}
	return w;
}

/*
	======================
	ppGrid2DFunction
	Создает патч, точки которого отодвинуты от одной из координатных плоскосей
	на величину, определяемую функцией function. В качестве аргументов функции
	передаются:
		basenum == 0:  x и y;
		basenum == 1:  x и z;
		basenum == 2:  y и z.
	Вход:
		- четыре числа, определяющие углы прямоугольника на координатной плоскости
		- количества разбиений по направлениям l-r и t-b;
		- флаг создания расширенной геометрической информации о элементах патча 
			(см. patDefaultGeometry())
		- функция
		- номер координатной плоскости (0 - xOy, 1 - xOz, 2 - yOz)
	Выход:
		патч.
	======================
*/

tripatch_p V_GLOBAL ppGridFunction(vec_t l,vec_t t,vec_t r,vec_t b,int hsplit,
	int vsplit,vboolean extinfo,planar_func_t planar,int basenum)
{
	int count;
	int i1, i2, i3;
	tripatch_p p;
	vec3_t curvert;
	if(!planar)
	{
		conlPuts(LOG_WARNING,"ppGridFunction(): planar==NULL\n");
		return NULL;
	}
	/* Замечание. Патч всегда создается на плоскости xOy. После создания, в случае
	   basenum != 0, координаты треугольников патча меняются местами.
	*/
	switch(basenum)
	{
		case BASE_XOZ:   i1=0; i2=2; i3=1; /* y=f(x,z); - для ск OpenGL */
			break;
		case BASE_YOZ:   i1=1; i2=2; i3=0; /* x=f(y,z) */
			break;
		default:  i1=0; i2=1; i3=2; /* z=f(x,y); */
			break;
	}
	p=patCreateRectangular(l,t,r,b,hsplit,vsplit,extinfo);
	if(!p) return NULL;
	conlPrintf(LOG_INFO,"ppGridFunction(): %d total vertices...\n",p->numvert);
	for(count=0;count<p->numvert;count++) {
		vec3Copy(curvert,p->vlist[count]);
		p->vlist[count][i1]=curvert[0];
		p->vlist[count][i2]=curvert[1];
		p->vlist[count][i3]=(vec_t)
			planar((vec_t)curvert[0],(vec_t)curvert[1]);
	}
	if(extinfo)
		patDefaultGeometry(p);
	return p;
}

/*
	======================
	ppGridSheffePolynom
	Создает патч, точки которого отодвинуты от одной из координатных плоскостей
	на величину, определяемую приведенным полиномом. В качестве аргументов
	передаются координаты точки на смесевом треугольнике (не существующем в
	геометрическом пространстве !), которая на сетке находится там же (хотел
	сказать "топологически соответствует", но это бред), что и текущая точка
	патча.
	Вход:
		- центр треугольника, радиус описанной около него окружности и направление
			на первую вершину. Эти параметры НЕ оказывают влияния на результат 
			вычисления полинома Шеффе.
		- количество разбиений каждой стороны;
		- флаг создания расширенной геометрической информации о элементах патча 
			(см. patDefaultGeometry())
		- массив из трех точек, расположенных внутри смесевого треугольника. Если
			передается NULL, то в качестве трех точек беруться {1,0,0} ... {0,0,1}.
			Эти три точки служат для построения сетки, координаты узлов которой
			используются в качестве аргументов полинома Шеффе.
		- приведенный полином
		- номер координатной плоскости (0 - xOy, 1 - xOz, 2 - yOz)
	Выход:
		патч.
	======================
*/

tripatch_p V_GLOBAL ppGridSheffePolynom(vec3_t center,vec_t radius,vec_t angle0,
	int split,vboolean extinfo,vec3_t *gibbs_abc,spatial_func_t sheffe,
	int basenum)
{
	int i,i1,i2,i3;
	tripatch_p pg, p;
	vec3_t curvert;
	vec3_t real_abc[3];
	vec3_t default_gibbs_abc[3]={{1,0,0},{0,1,0},{0,0,1}};
	if(!sheffe)
	{
		conlPuts(LOG_WARNING,"ppGridSheffePolynom(): sheffe==NULL\n");
		return NULL;
	}
	if(!gibbs_abc)
	{
		conlPuts(LOG_INFO,
			"ppGridSheffePolynom(): gibbs_abc==NULL, using default.\n");
		gibbs_abc=default_gibbs_abc;
	}
	for(i=0;i<3;i++)
	{
		vec3Copy(real_abc[i],center);
		real_abc[i][0]+=radius*(vec_t)cos(angle0+M_2PI*i/3);
		real_abc[i][1]+=radius*(vec_t)sin(angle0+M_2PI*i/3);
	}
	switch(basenum)
	{
		case BASE_XOZ:   i1=0; i2=2; i3=1;
			break;
		case BASE_YOZ:   i1=1; i2=2; i3=0;
			break;
		default:  i1=0; i2=1; i3=2;
			break;
	}
	pg=patCreateTriangular(gibbs_abc[0],gibbs_abc[1],gibbs_abc[2],split,0);
	p=patCreateTriangular(real_abc[0],real_abc[1],real_abc[2],split,extinfo);
	if(!p || !pg)
		return NULL;
	conlPrintf(LOG_INFO,
		"ppGridSheffePolynom(): %d total vertices...\n",p->numvert);
	for(i=0;i<p->numvert;i++)
	{
		vec3Copy(curvert,p->vlist[i]);
		p->vlist[i][i1]=curvert[0];
		p->vlist[i][i2]=curvert[1];
		p->vlist[i][i3]=(vec_t)
			sheffe((vec_t)pg->vlist[i][0],(vec_t)pg->vlist[i][1],(vec_t)pg->vlist[i][2]);
	}
	patFree(pg);
	if(extinfo)
		patDefaultGeometry(p);
	return p;
}

static patchprofile_p ppAlloc(int splits)
{
	patchprofile_p res;
	int size;
	size=sizeof(patchprofile_t)+splits*(sizeof(wirechains_p)+sizeof(vec_t));
	res=(patchprofile_p)memAlloc(size);
	memZero(res,size);
	res->splits=splits;
	res->wires=(wirechains_p*)(res+1);
	res->chainvals=(vec_t*)(res->wires+splits);
	return res;
}

void V_GLOBAL ppFree(patchprofile_p pp)
{
	int i;
	for(i=0;i<pp->splits;i++)
		wireFree(pp->wires[i]);
	memFree(pp);
}

static void baseplane_and_dir(plane3_t *pl,int *dir,int baseplane)
{
	switch(baseplane)
	{
		case BASE_XOZ:
			vec3Set(pl->normal,0,1,0);
			*dir=1;
			break;
		case BASE_YOZ:
			vec3Set(pl->normal,1,0,0);
			*dir=0;
			break;
		default:
			vec3Set(pl->normal,0,0,1);
			*dir=2;
			break;
	}
	pl->dist=0;
}

patchprofile_p V_GLOBAL ppCreateRectangular(
	vec_t l,vec_t t,vec_t r,vec_t b,
	planar_func_t planar,
	int zsplits,
	int baseplane)
{
	plane3_t pl;
	int i,c,iz;
	tripatch_p pat;
	patchprofile_p res;
	vec_t val,step;
	vec3_t *rawlines,*lines;
	int numchains;
	int numlines;
	int *indices;
	int *numind;
	if(zsplits<1)
		zsplits=1;
	conlPuts(LOG_INFO,"ppCreateRectangular(): creating 3D - surface...\n");
	pat=ppGridFunction(l,t,r,b,INTERNAL_SPLIT,INTERNAL_SPLIT,0,planar,baseplane);
	if(!pat)
	{
		conlPuts(LOG_WARNING,"ppCreateRectangular(): can't create surface.\n");
		return NULL;
	}
	res=ppAlloc(zsplits);
	res->baseplane=baseplane;
	res->planar=planar;
	memCopy(res->base,pat->base,3*sizeof(vec3_t));
	
	/* (b - a) + (c - a) */
	vec3Sub(res->base[3],res->base[1],res->base[0]);  /* ab */
	vec3Add(res->base[3],res->base[3],res->base[2]);  /* ab + c */
	vec3Sub(res->base[3],res->base[3],res->base[0]);  /* ab + ac */
	baseplane_and_dir(&pl,&iz,baseplane);
	res->fmin=pat->vlist[0][iz];
	res->fmax=res->fmin;
	for(i=0;i<pat->numvert;i++)
	{
		val=pat->vlist[i][iz];
		if(res->fmin > val) res->fmin=val;
		if(res->fmax < val) res->fmax=val;
	}
	step=(res->fmax-res->fmin)/(zsplits+1);
	if(IsZero(step))
	{
		conlPuts(LOG_WARNING,"ppCreateRectangular(): void bounding box.\n");
		patFree(pat);
		ppFree(res);
		return NULL;
	}
	conlPrintf(LOG_INFO,"ppCreateRectangular(): intersecting surface with %d planes...\n",
		zsplits);
	pl.dist=-res->fmin;
	for(i=0;i<zsplits;i++)
	{
		pl.dist-=step;
		res->chainvals[i]=-pl.dist;
		rawlines=lines=NULL;
		conlPrintf(LOG_INFO,"ppCreateRectangular(): plane %d ...\n",i);
		patIntersectWithPlane(&rawlines,&numlines,pat,&pl);
		if(!numlines)
		{
			conlPrintf(LOG_WARNING,
				"ppCreateRectangular(): no splits with plane %d...\n",i);
			continue;
		}
		conlPrintf(LOG_INFO,"ppCreateRectangular(): %d raw lines ...\n",numlines);
		uuRemoveDegenerateLines(&lines,rawlines,&numlines,false);
		if(!numlines)
		{
			conlPrintf(LOG_WARNING,
				"ppCreateRectangular(): all lines (plane %d) degenerate.\n",i);
			continue;
		}
		conlPrintf(LOG_INFO,"ppCreateRectangular(): %d lines ...\n",numlines);
		numchains=uuChainsFromLines(&indices,&numind,lines,numlines);
		if(!numchains)
		{
			conlPrintf(LOG_WARNING,
				"ppCreateRectangular(): (plane %d) no chains.\n",i);
			continue;
		}
		conlPrintf(LOG_INFO,"ppCreateRectangular(): %d chains ...\n",numchains);
		res->wires[i]=uuWireFromChains(lines,numind,indices,numchains,false);
		if(res->wires[i])
		{
			for(c=0;c<numchains;c++)
			{
				if(res->wires[i]->llist[c].flags & LF_NOLABEL) continue;
				res->wires[i]->llist[c].value=-pl.dist;
				sprintf(res->wires[i]->llist[c].text,"%2.4g",-pl.dist);
			}
		} else
			conlPrintf(LOG_WARNING,
				"ppCreateRectangular(): (plane %d) no chains.\n",i);
	}
	patFree(pat);
	return res;
}

patchprofile_p V_GLOBAL ppCreateTriangular(
	vec3_t center,vec_t radius,vec_t angle0,
	vec3_t *gibbs_abc,
	spatial_func_t sheffe,
	int zsplits,
	int baseplane)
{
	plane3_t pl;
	int i,c,iz;
	tripatch_p pat;
	patchprofile_p r;
	vec_t val,step;
	vec3_t *rawlines,*lines;
	int numchains;
	int numlines;
	int *indices;
	int *numind;
	if(zsplits<1)
		zsplits=1;
	conlPuts(LOG_INFO,"ppCreateTriangular(): creating 3D - surface...\n");
	pat=ppGridSheffePolynom(center,radius,angle0,INTERNAL_SPLIT,0,
		gibbs_abc,sheffe,baseplane);
	if(!pat)
	{
		conlPuts(LOG_WARNING,"ppCreateTriangular(): can't create surface.\n");
		return NULL;
	}
	r=ppAlloc(zsplits);
	r->baseplane=baseplane;
	r->spatial=sheffe;
	memCopy(r->base,pat->base,3*sizeof(vec3_t));
	baseplane_and_dir(&pl,&iz,baseplane);
	r->fmin=pat->vlist[0][iz];
	r->fmax=r->fmin;
	for(i=0;i<pat->numvert;i++)
	{
		val=pat->vlist[i][iz];
		if(r->fmin > val) r->fmin=val;
		if(r->fmax < val) r->fmax=val;
	}
	step=(r->fmax-r->fmin)/(zsplits+1);
	if(IsZero(step))
	{
		conlPuts(LOG_WARNING,"ppCreateTriangular(): void bounding box.\n");
		patFree(pat);
		ppFree(r);
		return NULL;
	}
	conlPrintf(LOG_INFO,"ppCreateTriangular(): intersecting surface with %d planes...\n",
		zsplits);
	pl.dist=-r->fmin;
	for(i=0;i<zsplits;i++)
	{
		pl.dist-=step;
		r->chainvals[i]=-pl.dist;
		rawlines=lines=NULL;
		conlPrintf(LOG_INFO,"ppCreateTriangular(): plane %d ...\n",i);
		patIntersectWithPlane(&rawlines,&numlines,pat,&pl);
		if(!numlines) {
			conlPrintf(LOG_WARNING,
				"ppCreateTriangular(): no splits with plane %d...\n",i);
			continue;
		}
		conlPrintf(LOG_INFO,"ppCreateTriangular(): %d raw lines ...\n",numlines);
		uuRemoveDegenerateLines(&lines,rawlines,&numlines,false);
		if(!numlines) {
			conlPrintf(LOG_WARNING,
				"ppCreateTriangular(): all lines (plane %d) degenerate.\n",i);
			continue;
		}
		conlPrintf(LOG_INFO,"ppCreateTriangular(): %d lines ...\n",numlines);
		numchains=uuChainsFromLines(&indices,&numind,lines,numlines);
		if(!numchains) {
			conlPrintf(LOG_WARNING,
				"ppCreateTriangular(): (plane %d) no chains.\n",i);
			continue;
		}
		conlPrintf(LOG_INFO,"ppCreateTriangular(): %d chains ...\n",numchains);
		r->wires[i]=uuWireFromChains(lines,numind,indices,numchains,false);
		if(r->wires[i])
		{
			for(c=0;c<numchains;c++)
			{
				if(r->wires[i]->llist[c].flags & LF_NOLABEL) continue;
				r->wires[i]->llist[c].value=-pl.dist;
				sprintf(r->wires[i]->llist[c].text,"%2.4g",-pl.dist);
			}
		} else
			conlPrintf(LOG_WARNING,
				"ppCreateTriangular(): (plane %d) no chains.\n",i);
	}
	patFree(pat);
	return r;
}

/*</pre></html>*/
/*EOF*/
