This text file contains two files, which when used in conjunction with the
Manchester NURBS library (available from unix.hensa.ac.uk) provide a basic 
skinning operation.

They were written originally to allow me to 'skin' multiple NURBS surfaces,
to produce a hyper-surface (or 4D NURBS depending on your outlook) for 
animation purposes. I've culled the morphing code from the files to keep 
things clear.

As a result of this the algorithm as implemented has one glaring problem.
The surface you create from multiple 2D NURBS sections will have order =
number of sections. This might not be quite what you want, but it was 
good enough for me! :-)

To use the code compile and link with the NURBS library. 
Then do something like .....

  SectionRecord *the_list;
  PR_nurb first, last, output;

  nrb_initialise();

  /* Create your nurbs */

  CreateSectionList(&the_list);
  CreateNewSection(the_list,&first,0.0);
  CreateNewSection(the_list,&last,0.0);

  skin_2to3(the_list,&spine,0,
	    &output);

and now the PR_nurb output should contain the surface!

The NURBS library contains routines to tessalate the surface, presumably
so it can be fed back into < Insert your favourite software here>.

Please let me know if you have any difficulties using it, as i said this
is a culled version of the software, and i haven't tested it as is! :-)

Martin


----------------------------------

/* Skinning.h : Header file for skinning routines */

/* Martin Preston, University of Manchester, Computer Graphics Unit,
   Manchester, U.K. (Email: preston@cs.man.ac.uk)
*/

typedef struct SectionRecord 
{
	PR_nurb nurb;
	float t;
	struct SectionRecord *next;
}SectionRecord;

typedef PR_nurb PR_nurb4;

Ppoint4 ptk_point4(float x, float y, float z, float w);
Pvec3 ptk_subp(Ppoint3* first, Ppoint3 * second);
void ptk_absv3(Pvec3* input);
void CreateNewSection(SectionRecord *the_list, 
		 PR_nurb *the_nurb, 
		 float the_t);
void CreateSectionList(SectionRecord **the_list);
void skin_2to3(SectionRecord *sections,PR_nurb *spine,int translate,
	  PR_nurb *out);

typedef PR_nurb PR_nurb4;

-----------------------------------------------

/* Skinning.c : C code for skinning routines */

/* Martin Preston, University of Manchester, Computer Graphics Unit,
   Manchester, U.K. (Email: preston@cs.man.ac.uk)
*/

/* ***********************************************************************
   *        SKINNING MODULE                                              *
   *   Started : 18/11/91                                                *
   *********************************************************************** */
/* Have changed :
   Updated ptk_? calls to use new version of library
   Changed pf_x & pf_v to pf_u & pf_v
         11/11/92 
*/
#include <stdlib.h> 
#define TRUE 1
#define FALSE 0
#define NIL 0

#include "skinning.h"

// Hacks to fix for this new version of ptk
typedef PR_nurb PR_nurb4;

Ppoint4 ptk_point4(float x, float y, float z, float w)
{
  Ppoint4 tmp;

  tmp.x = x;
  tmp.y = y;
  tmp.z = z;
  tmp.w = w;

  return(tmp);
}


Pvec3 ptk_subp(Ppoint3* first,Ppoint3* second)
{
	/* Complete Hack to do part of ptk library */
	Pvec3 firstv,secondv;
	Pvec3 temp;

	firstv= ptk_pt3tovec3(first);
	secondv = ptk_pt3tovec3(second);
	temp = ptk_subv(&firstv,&secondv);

	return(temp);
}

void ptk_absv3(Pvec3* input)
{
	/* Another function in the popular 'make ptk work' series */
	/* This calculates the absolute/positive value of vector */
	input->x = (input->x < 0) ? (input->x * -1) : input->x;
	input->y = (input->y < 0) ? (input->y * -1) : input->y;
	input->z = (input->z < 0) ? (input->z * -1) : input->z;
}

void CreateNewSection(SectionRecord *the_list, 
		 PR_nurb *the_nurb, 
		 float the_t)
{
	/* This function adds a NURB onto the end of a section list */
	SectionRecord *current;

	current = the_list;
	while((current->next != NIL) && (current->t != -1))
		current = current->next;

	if((current->next == NIL) && (current->t != -1))
	{
		current->next = (SectionRecord *) malloc(sizeof(SectionRecord));
		current = current->next;	
	}
	nrb_copy(the_nurb,&current->nurb);
	current->t = the_t;
	current->next = NIL;
}

void CreateSectionList(SectionRecord **the_list)
{
	/* This function creates a section list */
	*the_list = (SectionRecord *) malloc(sizeof(SectionRecord));
	nrb_clear(&(*the_list)->nurb);
	(*the_list)->t = -1;
	(*the_list)->next = NIL;
}

int knot_ind(int num, int order)
{
	/* This simple function calculates an index into the knot record of
	   a particular knot */
	return((order-1)+num);
}

void normalise(SectionRecord *sections,int direction,int num_sections,int *max_order,
	  int *max_pts,int *end_numpts,PR_nurb* temp_nurb2)
{
	/* This procedure normalises a section list,ie it ensures everything is 
    elevated to the correct order,and all sections have the correct 
    number of control points .

    If called with direction = TRUE it normalises everything in the x
    direction,and in the y if direction = FALSE. */

	int current_section,i; 
	PR_nurb temp_nurb,knot_nurb;
	SectionRecord *list;

	*max_order = *max_pts = *end_numpts = 0;
	list = sections;

	nrb_clear(&temp_nurb);

	if(direction)
	{
		/* Necessary to tranpose everything */
		for(current_section=1; current_section<=num_sections;current_section++)
		{
			nrb_transpose(&list->nurb);
			list = list->next;
		}
	}
	list = sections;
	for(current_section=1; current_section<=num_sections; current_section++)
	{
		if(list->nurb.pf_u.pf_k > *max_order)
			*max_order = list->nurb.pf_u.pf_k;
		list = list->next;	
	}

	list = sections;
	/* Now change required parts */
	for(current_section=1; current_section<=num_sections;current_section++)
	{
		while(list->nurb.pf_u.pf_k < *max_order)
		{
			nrb_clear(&temp_nurb);
			nrb_elevate(FALSE,&list->nurb,&temp_nurb);
			nrb_copy(&temp_nurb,&list->nurb);
		}
		if(list->nurb.pf_u.pf_n > *max_pts)
		{
			*max_pts = list->nurb.pf_u.pf_n;
			*end_numpts += list->nurb.pf_u.pf_n;
		}
		list = list->next;

	}
	list = sections;
	
	/* Now initialise */
	temp_nurb2->pf_u.pf_k = *max_order;
	temp_nurb2->pf_u.pf_n = list->nurb.pf_u.pf_n;
	temp_nurb2->pf_u.pf_nt = temp_nurb2->pf_u.pf_n + temp_nurb2->pf_u.pf_k;
	temp_nurb2->pf_u.pf_kk = NIL;

	temp_nurb2->pf_v.pf_k = list->nurb.pf_v.pf_k;
	temp_nurb2->pf_v.pf_n = list->nurb.pf_v.pf_n;
	temp_nurb2->pf_v.pf_nt = temp_nurb2->pf_v.pf_n + temp_nurb2->pf_v.pf_k;
	temp_nurb2->pf_v.pf_kk = NIL;

	temp_nurb2->pf_ppp = NIL;

	temp_nurb.pf_u.pf_k = *max_order;
	temp_nurb.pf_u.pf_n = list->nurb.pf_u.pf_n;
	temp_nurb.pf_u.pf_nt = temp_nurb2->pf_u.pf_n + temp_nurb2->pf_u.pf_k;
	temp_nurb.pf_u.pf_kk = NIL;

	temp_nurb.pf_v.pf_k = list->nurb.pf_v.pf_k;
	temp_nurb.pf_v.pf_n = list->nurb.pf_v.pf_n;
	temp_nurb.pf_v.pf_nt = temp_nurb2->pf_v.pf_n + temp_nurb2->pf_v.pf_k;
	temp_nurb.pf_v.pf_kk = NIL;

	temp_nurb.pf_ppp = NIL;

	nrb_allocateknots(&temp_nurb2->pf_u);
	nrb_allocateknots(&temp_nurb2->pf_v);
	nrb_allocateknots(&temp_nurb.pf_u);
	nrb_allocateknots(&temp_nurb.pf_v);
	nrb_allocatepts((temp_nurb2->pf_u.pf_nt*temp_nurb2->pf_v.pf_nt)*2,
                  &temp_nurb2->pf_ppp);
	nrb_allocatepts((temp_nurb.pf_u.pf_nt*temp_nurb.pf_v.pf_nt)*2,
                  &temp_nurb.pf_ppp);
	for(i=0; i< (temp_nurb2->pf_u.pf_nt*temp_nurb2->pf_v.pf_nt*2); i++)
	{
		temp_nurb.pf_ppp->pts[i] = ptk_point4(0.0,0.0,0.0,0.0);
		temp_nurb2->pf_ppp->pts[i] = ptk_point4(0.0,0.0,0.0,0.0);
	}
	for(i=0; i<list->nurb.pf_u.pf_nt; i++)
		temp_nurb2->pf_u.pf_kk->knots[i] =
			list->nurb.pf_u.pf_kk->knots[i];
	for(i=0; i<list->nurb.pf_v.pf_nt; i++)
		temp_nurb2->pf_v.pf_kk->knots[i] =
			list->nurb.pf_v.pf_kk->knots[i];
	nrb_copy(temp_nurb2,&knot_nurb);

	list = sections;
	for(current_section=1; current_section<=num_sections; current_section++)
	{
		nrb_unionknots(&list->nurb.pf_u,&temp_nurb2->pf_u,&knot_nurb.pf_u);
		nrb_copyknots(&knot_nurb.pf_u,&temp_nurb2->pf_u);
		list = list->next;
	}

	list = sections;
	for(current_section=1; current_section<=num_sections; current_section++)
	{
		/* Perform Oslo algorithm to increase pts */
		nrb_copyknots(&list->nurb.pf_v,&temp_nurb2->pf_v);
		nrb_copy(temp_nurb2,&temp_nurb);
		nrb_osloc(&list->nurb,temp_nurb2);
		nrb_transpose(temp_nurb2);
		nrb_clear(&list->nurb);	
		nrb_copy(temp_nurb2,&list->nurb);
		nrb_copy(&temp_nurb,temp_nurb2);
		list = list->next;
	}
	list = sections;

	if(direction)
	{
		/* Necessary to tranpose everything */
		for(current_section=1; current_section<=num_sections;
	current_section++)
		{
			nrb_transpose(&list->nurb);
			list = list->next;
		}
	}
}

void skin_2to3(SectionRecord *sections,PR_nurb *spine,int translate,
	  PR_nurb *out)
{
	int current_section,num_sections,current_knot,end_numpts;
	int max_order,max_pts;
	Pmatrix3 current_matrix;
	PR_nurb temp_nurb, temp_nurb2;
	SectionRecord *list;
	Ppoint3 q1;
	Ppoint3 current_point,next_point,last_point;
	Pvec3 normal, orientation, direction,dir1,dir2,q2,q3, tempv;
	int pt,i;

	max_order = max_pts = end_numpts = 0;
	current_knot = 1;
	list = sections;

	num_sections = spine->pf_u.pf_n;
	/* Traverse list to determine settings */
	normalise(list,FALSE,num_sections,&max_order,&max_pts,&end_numpts,&temp_nurb2);
	/* Initialise the output */
	nrb_clear(out);
	nrb_clear(&temp_nurb);

	/* Fill in the output NURB */
	out->pf_u.pf_k = max_order;
	out->pf_u.pf_n = end_numpts;
	out->pf_u.pf_nt = max_order + end_numpts;
		/* The unioned knots should already have been filled in */
	out->pf_v.pf_n = spine->pf_u.pf_n;
	out->pf_v.pf_k = spine->pf_u.pf_k;
	out->pf_v.pf_nt = spine->pf_u.pf_n + spine->pf_u.pf_k;

	nrb_allocateknots(&out->pf_u);
	nrb_allocateknots(&out->pf_v);
	nrb_allocatepts((out->pf_u.pf_n)*(out->pf_v.pf_n),&out->pf_ppp);
	
	/* Now copy x knots from so_far into x knots of output */
	for(i=0; i<temp_nurb2.pf_u.pf_nt; i++)
		out->pf_u.pf_kk->knots[i] = list->nurb.pf_u.pf_kk->knots[i];
	/* Now copy x knots from spine into y knots of output */
	for(i=0; i<(out->pf_v.pf_nt); i++)
		out->pf_v.pf_kk->knots[i] = spine->pf_u.pf_kk->knots[i];
	list = sections;

	for(current_section=1; current_section<=num_sections; current_section++)
	{

		if(translate)
		{
			/* Calculate current position */
			current_point =ptk_pt4topt3(&spine->pf_ppp->pts[current_section]);
			if(current_section==num_sections)
				next_point=ptk_pt4topt3(&spine->pf_ppp->pts[1]);
			else
				next_point=ptk_pt4topt3(&spine->pf_ppp->pts[current_section+1]);
			if(current_section==1)
				last_point=ptk_pt4topt3(&spine->pf_ppp->pts[num_sections]);
			else
				last_point=ptk_pt4topt3(&spine->pf_ppp->pts[current_section-1]);
			/* Place section at correct point by ... */
			dir1 = ptk_subp(&next_point,&current_point);
			dir2 = ptk_subp(&current_point,&last_point);
		        dir1 = ptk_addv(&dir2,&dir1);
			direction = ptk_scalev(&dir1,0.5);

			/* Calculate normal */
			dir2 = ptk_vector3(0.0,0.0,1.0);
			dir1 = ptk_crossv(&direction,&dir2);   /* Temporary normal */
			orientation = ptk_vector3(0.0,0.0,1.0);
			ptk_absv3(&dir1);

			/* Calculate translations */

			q1 = current_point;
			tempv = ptk_pt3tovec3(&q1);
			q2 = ptk_addv(&dir1,&tempv);
			q3 = ptk_addv(&direction,&tempv);
			normal = ptk_unitv(&dir1);

			tempv = ptk_pt3tovec3(&current_point);
			dir1 = ptk_addv(&dir2,&tempv);
			dir2 = ptk_addv(&normal,&tempv);

			ptk_0to3pt(&current_point,&dir1
				,&dir2
				,PREPLACE,current_matrix);
			nrb_xform(&list->nurb,current_matrix);
		}
		/* Add to NURB structure */
		for(pt=1; pt<=max_pts; pt++)
			out->pf_ppp->pts[nrb_index(pt,current_section,max_pts)-1]
			  = list->nurb.pf_ppp->pts[nrb_index(pt,1,max_pts)-1];
		list = list->next;
	}
}



/* End of Skinning module */


