#include "view.h"

Face *newFace(vertex,i)
Vertex *vertex;
int i;
{
	Face *p;
	
	p = Malloc(Face,1);
	p->vertex = vertex;
	
	p->first = newIndex(i);
	p->last = p->first;
	p->last->next = p->first;
	p->size = 1;
	p->shading = NOSHADING;
	p->coeff = NOPLANEQ;
	p->lightsAway = NOINDEX;
	p->noOfShadows = 0;
	p->shadow = NOINDEX;
	p->lastShadow = NOINDEX;
	return(p);
}

void addToFrontFace(poly,i)
Face *poly;
int i;
{
	poly->first = addToFrontIndex(poly->first,i);
	poly->last->next = poly->first;
	++poly->size;
}


void addToBackFace(poly,i)
Face *poly;
int i;
{
	poly->last = addToBackIndex(poly->last,i);
	poly->last->next = poly->first;
	++poly->size;
}

Vertex *vertexOfPoly(poly)
Face *poly;
{
	return(poly->vertex);
}

Index *firstIndexInFace(poly)
Face *poly;
{
	return(poly->first);
}

Index *lastIndexInFace(poly)
Face *poly;
{
	return(poly->last);
}

int sizeOfFace(poly)
Face *poly;
{
	return(poly->size);
}

void traverseFace(poly,f)
Face *poly;
void (*f)();
{
	Index *ind;
	
	if(poly == NOFACE) return;
	
	ind = poly->first;
	while(ind != poly->last)
	{
		(*f)(poly->vertex,ind->i);
		ind = ind->next;
	}
	(*f)(poly->vertex,ind->i);
}

void traversePtrFace(poly,f,ptr)
Face *poly;
void (*f)();
Ptr ptr;
{
	Index *ind;
	
	if(poly == NOFACE) return;
	
	ind = poly->first;
	while(ind != poly->last)
	{
		(*f)(poly->vertex,ind->i,ptr);
		ind = ind->next;
	}
	(*f)(poly->vertex,ind->i,ptr);
}

void traverseEdgesOfFace(poly,f,ptr)
Face *poly;
void (*f)();
Ptr ptr;
{
	Index *ind1,*ind2;
	Point3D *p1,*p2,*p;
	ind1 = poly->first;
	ind2 = NEXT(ind1);
	p = p1 = atVertex(poly->vertex,ind1->i);
	while(ind2 != poly->first){
		p2 = atVertex(poly->vertex,ind2->i);
		(*f)(p1,p2,ptr);
		p1 = p2;
		ind2 = NEXT(ind2);
	}
	(*f)(p1,p,ptr);
}

int getnVerticesOfFace(poly,n,vertices)
/*returns the number of vertices allocated*/
Face *poly;
int n;
Point3D vertices[];
{	
	int m;
	Index *ind;
	int i;
	
	m = sizeOfFace(poly);
	if(m > n) m = n;
	
	ind = firstIndexInFace(poly);
	
	for(i=0;i<m;++i){
		vertices[i] = *getPointFromIndex(VERTEXOfFace(poly),ind);
		ind = nextIndex(ind);
	}
	return(m);
}

void setPlaneEqOfFace(poly)
Face *poly;
{
	Point3D p[3];
	PlaneEq *coeff;
	
	PLANEQOfFace(poly) = NOPLANEQ;
	if(getnVerticesOfFace(poly,3,p) < 3) return;
		
	coeff = Malloc(PlaneEq,1);
	normalPoint3D(p,coeff);
	coeff->d = detFor3Point3D(p);
	PLANEQOfFace(poly) = coeff;
}

void setShadingOfFace(poly,shading)
Face *poly;
Shading *shading;
{
	poly->shading = shading;
}

void getCentreOfFace(poly,centre)
/*returns in centre the centre of gravity of the polygon*/
Face *poly;
Point3D *centre;
{
	Point3D *p;
	Index *ind;
	
	if(poly == NOFACE) return;
	
	ind = poly->first;
	p = atVertex(poly->vertex,ind->i);
	centre->x = p->x;
	centre->y = p->y;
	centre->z = p->z;
	while(ind != poly->last)
	{
		ind = ind->next;
		p = atVertex(poly->vertex,ind->i);
		centre->x += p->x;
		centre->y += p->y;
		centre->z += p->z;
	}
	centre->x /= (SIZEOfFace(poly));
	centre->y /= (SIZEOfFace(poly));
	centre->z /= (SIZEOfFace(poly));
}

void setDirectionalLightingOfFace(face,lighting)
/*computes lighting for simple Lambert model with light source
  at infinity*/
Face *face;
Lighting *lighting;
{	
	register int i;
	Vector n;
	REAL nl,r,g,b,red,green,blue;
	
	REDOfFace(face)   = REDAmbient(lighting)*REDReflectionOf(face);
	GREENOfFace(face) = GREENAmbient(lighting)*GREENReflectionOf(face);
	BLUEOfFace(face)  = BLUEAmbient(lighting)*BLUEReflectionOf(face);
	
	/*normal to the face*/
	MAKEVector(n,PLANEQOfFace(face)->a,
				 PLANEQOfFace(face)->b,
				 PLANEQOfFace(face)->c);
				 
	/*for each light source add the intensity*/
	if(NOOfLightsIn(lighting) > 0){
		red = green = blue = 0.0;
		for(i=0; i<NOOfLightsIn(lighting); ++i){		
			r = REDLighting(lighting,i);		
			g = GREENLighting(lighting,i);		
			b = BLUELighting(lighting,i);		
			nl = PRODUCTVector(n,DIRECTIONOf(lighting,i));
			nl /= (NORMVector(n)*NORMVector(DIRECTIONOf(lighting,i)));
			if(NEGATIVE(nl)) nl = 0.0;
			red   += (r*REDReflectionOf(face)*nl);
			green += (g*GREENReflectionOf(face)*nl);
			blue  += (b*BLUEReflectionOf(face)*nl);
		}
		REDOfFace(face)   += red;
		GREENOfFace(face)   += green;
		BLUEOfFace(face)   += blue;
	}
}


void setPositionalLightingOfFace(face,lighting)
/*computes lighting for simple Lambert model with light source
  at a position and centre of face*/
Face *face;
Lighting *lighting;
{	
	register int i;
	Vector n;
	REAL nl,r,g,b,red,green,blue;
	Point3D c,lightpos;
	Vector direction;
	
	REDOfFace(face)   = REDAmbient(lighting)*REDReflectionOf(face);
	GREENOfFace(face) = GREENAmbient(lighting)*GREENReflectionOf(face);
	BLUEOfFace(face)  = BLUEAmbient(lighting)*BLUEReflectionOf(face);
	
	/*normal to the face*/
	MAKEVector(n,PLANEQOfFace(face)->a,
				 PLANEQOfFace(face)->b,
				 PLANEQOfFace(face)->c);
	
	/*compute the  the centre of polygon*/
	getCentreOfFace(face,&c);
				 
	red = green = blue = 0.0;
	/*for each light source add the intensity*/
	if(NOOfLightsIn(lighting) > 0){
		for(i=0; i<NOOfLightsIn(lighting); ++i){		
			r = REDLighting(lighting,i);		
			g = GREENLighting(lighting,i);		
			b = BLUELighting(lighting,i);	
			lightpos = POSITIONOf(lighting,i);	
			MAKEVector(direction,lightpos.x-c.x,lightpos.y-c.y,lightpos.z-c.z);
			nl = PRODUCTVector(n,direction);
			nl /= (NORMVector(n)*NORMVector(direction));
			if(NEGATIVE(nl)) nl = 0.0;
			red   += (r*REDReflectionOf(face)*nl);
			green += (g*GREENReflectionOf(face)*nl);
			blue  += (b*BLUEReflectionOf(face)*nl);
		}
		REDOfFace(face)   += red;
		GREENOfFace(face)   += green;
		BLUEOfFace(face)   += blue;
	}
}


void getColourAtPointOnFace(face,p,lighting,colour)
/*gets the colour at the ith vertex of the face - excluding ambient light*/
Face *face;
Point3D *p;
Lighting *lighting;
Colour *colour;
{
	register int i;
	Vector n;
	REAL nl,r,g,b;
	Point3D lightpos;
	Vector direction;
	
	/*normal to the face*/
	MAKEVector(n,PLANEQOfFace(face)->a,
				 PLANEQOfFace(face)->b,
				 PLANEQOfFace(face)->c);
				 
	colour->red = colour->green = colour->blue = 0.0;
	/*for each light source add the intensity*/
	if(NOOfLightsIn(lighting) > 0){
		for(i=0; i<NOOfLightsIn(lighting); ++i){		
			r = REDLighting(lighting,i);		
			g = GREENLighting(lighting,i);		
			b = BLUELighting(lighting,i);	
			lightpos = POSITIONOf(lighting,i);	
			MAKEVector(direction,lightpos.x-p->x,lightpos.y-p->y,lightpos.z-p->z);
			nl = PRODUCTVector(n,direction);
			nl /= (NORMVector(n)*NORMVector(direction));
			if(POSITIVE(nl)){ 
				colour->red   += (r*REDReflectionOf(face)*nl);
				colour->green += (g*GREENReflectionOf(face)*nl);
				colour->blue  += (b*BLUEReflectionOf(face)*nl);
			}
		}
	}
}

void setAverageLightingOfFace(face,lighting)
/*sets the lighting of the face as the average of 
  the colour at the vertices and the centre
*/
Face *face;
Lighting *lighting;
{
	register Index *ind;
	register int i;
	REAL red,green,blue,n;
	Colour colour;
	Point3D *p,centre;
	
	REDOfFace(face)   = REDAmbient(lighting)*REDReflectionOf(face);
	GREENOfFace(face) = GREENAmbient(lighting)*GREENReflectionOf(face);
	BLUEOfFace(face)  = BLUEAmbient(lighting)*BLUEReflectionOf(face);
	
	
	ind = face->first;
	p = atVertex(face->vertex,ind->i);
	centre.x = p->x;
	centre.y = p->y;
	centre.z = p->z;
	getColourAtPointOnFace(face,p,lighting,&colour);
	red = colour.red;
	green = colour.green;
	blue = colour.blue;
	
	while(ind != face->last){
		ind = ind->next;
		p = atVertex(face->vertex,ind->i);
		getColourAtPointOnFace(face,p,lighting,&colour);
		red += colour.red;
		green += colour.green;
		blue += colour.blue;
		centre.x += p->x;
		centre.y += p->y;
		centre.z += p->z;
	}
	
	centre.x /= (SIZEOfFace(face));
	centre.y /= (SIZEOfFace(face));
	centre.z /= (SIZEOfFace(face));
	getColourAtPointOnFace(face,&centre,lighting,&colour);
	
	n = (REAL)(SIZEOfFace(face) + 1);
	REDOfFace(face)   += ((red+colour.red)/n);
	GREENOfFace(face) += ((green+colour.green)/n);
	BLUEOfFace(face)  += ((blue+colour.blue)/n);
}

void setAmbientLightingOnFace(face,ambient)
Face *face;
Light *ambient;
{
	REDOfFace(face)   = REDLight(ambient)*REDReflectionOf(face);
	GREENOfFace(face) = GREENLight(ambient)*GREENReflectionOf(face);
	BLUEOfFace(face)  = BLUELight(ambient)*BLUEReflectionOf(face);
}


void setCentreColourOfFace(face,light,lightNo)
/*this adds in the effect of this light on the colour of the face*/
Face *face;
Light *light;
int lightNo;
{
	Vector n;
	REAL nl;
	Point3D c;
	Vector direction;
	
	/*normal to the face*/
	MAKEVector(n,PLANEQOfFace(face)->a,
				 PLANEQOfFace(face)->b,
				 PLANEQOfFace(face)->c);
	
	/*compute the centre of polygon*/
	getCentreOfFace(face,&c);
				 
	MAKEVector(direction,
			   light->position.x-c.x,light->position.y-c.y,light->position.z-c.z);
	nl = PRODUCTVector(n,direction);
	nl /= (NORMVector(n)*NORMVector(direction));
	if(nl > 0.0){
		REDLightOfFace(face,lightNo) = REDLight(light)*REDReflectionOf(face)*nl;
		GREENLightOfFace(face,lightNo) = GREENLight(light)*GREENReflectionOf(face)*nl;
		BLUELightOfFace(face,lightNo) = BLUELight(light)*BLUEReflectionOf(face)*nl;
		
		/*increment no of lights*/
		face->shading->noOfLights++;
		
		REDOfFace(face)   += REDLightOfFace(face,lightNo);
		GREENOfFace(face) += GREENLightOfFace(face,lightNo);
		BLUEOfFace(face)  += BLUELightOfFace(face,lightNo);
	}
}



void getCentreColourOfFace(face,light,colour)
/*this returns in colour in the effect of this light on the colour of the face*/
Face *face;
Light *light;
Colour *colour;
{
	Vector n;
	REAL nl;
	Point3D c;
	Vector direction;
	
	/*normal to the face*/
	MAKEVector(n,PLANEQOfFace(face)->a,
				 PLANEQOfFace(face)->b,
				 PLANEQOfFace(face)->c);
	
	/*compute the centre of polygon*/
	getCentreOfFace(face,&c);
				 
	MAKEVector(direction,
			   light->position.x-c.x,light->position.y-c.y,light->position.z-c.z);
	nl = PRODUCTVector(n,direction);
	nl /= (NORMVector(n)*NORMVector(direction));
	colour->red = colour->green = colour->blue = 0.0;
	if(nl > 0.0){
		colour->red = (REDLight(light)*REDReflectionOf(face)*nl);
		colour->green = (GREENLight(light)*GREENReflectionOf(face)*nl);
		colour->blue =  (BLUELight(light)*BLUEReflectionOf(face)*nl);
	}
}


PlaneEq *copyPlaneEqOfFace(poly)
Face *poly;
{
	PlaneEq *coeff;
	if(PLANEQOfFace(poly) == NOPLANEQ) return(NOPLANEQ);
	
	coeff = Malloc(PlaneEq,1);
	coeff->a = poly->coeff->a;
	coeff->b = poly->coeff->b;
	coeff->c = poly->coeff->c;
	coeff->d = poly->coeff->d;
	
	return(coeff);
}

Shading *copyShadingOfFace(poly)
Face *poly;
{
	register int i;
	
	Shading *shading;
	if(SHADINGOfFace(poly) == NOSHADING) return(NOSHADING);
	
	shading = Malloc(Shading,1);
	shading->reflection = poly->shading->reflection;
	shading->colour = poly->shading->colour;
	
	if(poly->shading->noOfLights>0)
		for(i=0;i<poly->shading->noOfLights;++i)
			shading->colourOfLight[i] = poly->shading->colourOfLight[i];
	shading->noOfLights = poly->shading->noOfLights;
	
	return(shading);
}


Classification addClassification(a,b)
Classification a,b;
{
	switch(a){										
		case FRONT	: 	return(b==BACK?FRONT_BACK:FRONT);	

		case BACK	: 	return(b==FRONT?BACK_FRONT:BACK);

		case ON		: 	return(b);						

		default		: 	/*donothing*/				
						break;					
	}											
}

void printClassification(c)
Classification c;
{
	switch(c){
		case FRONT 		: printf("FRONT\n");
				      	  break;
		case BACK		: printf("BACK\n");
				      	  break;
		case ON			: printf("ON\n");
				      	  break;
		case FRONT_BACK	: printf("FRONT_BACK\n");
				      	  break;
		case BACK_FRONT	: printf("BACK_FRONT\n");
				      	  break;
		default	    	: printf("No Classification!\n");
				     	  break;
	}
}


void splitFace(poly,plane,c,ind1,ind2,t,frontP,backP)
/*
  split poly into two between indices ind1 and ind2.
  t is the t value for the intersection.
*/
Face *poly;
PlaneEq *plane;
Classification c;
Index *ind1,*ind2;
REAL t;
FacePtr *frontP,*backP;
{
	Vertex *v;
	Point3D *p1,*p2,*p;
	int k1,k2;
	Index *i1,*i2;
	Classification c1,c2,cn;
	REAL x,y,z,d1,d2;
	Face *theFront,*theBack;
	PlaneEq *pe;
	Shading *sh;
	BOOLEAN tZero;
	
	/*get the end points of the split edge*/
	v = VERTEXOfFace(poly);	
	p1 = GETPointFromIndex(v,ind1);	
	p2 = GETPointFromIndex(v,ind2);	
		
	if(ABS(t) > TOL){
		/*and compute the new point - inserting into the vertex list*/
		x = X(*p1) + (X(*p2) - X(*p1))*t;
		y = Y(*p1) + (Y(*p2) - Y(*p1))*t;
		z = Z(*p1) + (Z(*p2) - Z(*p1))*t;
		p = newPoint3D(x,y,z);
		k1 = NEXTFreeVertex(v);
		atPutVertex(v,k1,p);
		tZero = FALSE;
	}
	else{
		tZero = TRUE;
		p = p1;
		k1 = INDEX(ind1);
	}
		
	i2 = ind2;
	if(c==FRONT_BACK) c1 = BACK; else c1 = FRONT;
	c2 = c1;
	/*
	classifyPoint3D(plane,p2,&d2);
	*/
	CLASSIFYPoint3D(plane,(*p2),d2,cn);

	/*this is worked out as if going from FRONT to BACK*/
	/*start off the back face with this point and p2*/
	theBack = newFace(v,k1);
	while(c2 == c1){
		addToBackFace(theBack,INDEX(i2));
		i1 = i2;
		d1 = d2;
		i2 = NEXT(i2);
		p2 = GETPointFromIndex(v,i2);	
		/*
		c2 = addClassification(c2,classifyPoint3D(plane,p2,&d2));
		*/
		CLASSIFYPoint3D(plane,(*p2),d2,cn);
		ADDClassification(c2,cn,c2);
	}
		
	/*here c2 must be BACK_FRONT and the split is between i1, i2*/
	p1 = GETPointFromIndex(v,i1);	
	p2 = GETPointFromIndex(v,i2);				
	t = d1/(d1-d2);
	
	if(ABS(t) > TOL){
		x = X(*p1) + (X(*p2) - X(*p1))*t;
		y = Y(*p1) + (Y(*p2) - Y(*p1))*t;
		z = Z(*p1) + (Z(*p2) - Z(*p1))*t;
		p = newPoint3D(x,y,z);
		k2 = NEXTFreeVertex(v);
		atPutVertex(v,k2,p);
		addToBackFace(theBack,k2);
		
		theFront = newFace(v,k2);
	}
	else theFront = newFace(v,INDEX(i1));
	
	while(i2 != ind2){
		addToBackFace(theFront,INDEX(i2));
		i2 = NEXT(i2);
	}
	if(tZero==FALSE) addToBackFace(theFront,k1);
		
	if(c == FRONT_BACK){
		*frontP = theFront;
		*backP = theBack;
	}
	else{
		*frontP = theBack;
		*backP = theFront;
	}
	
	/*finally give frontP and backP the same plane equation
	  and shading as the original polygon*/
	pe = copyPlaneEqOfFace(poly);
	sh = copyShadingOfFace(poly);
	(*frontP)->coeff = pe;
	(*frontP)->shading = sh;
	pe = copyPlaneEqOfFace(poly);
	sh = copyShadingOfFace(poly);
	(*backP)->coeff = pe;
	(*backP)->shading = sh;
	/*note - had to make sure that all these pointers are different*/
}


Classification classifyFace(poly,plane,frontP,backP)
/*
  returns the classification of the face with respect
  to the plane. If the classification is CUT then frontP
  and backP are the respective new polygons
*/
Face *poly;
PlaneEq *plane;
FacePtr *frontP,*backP;
{
	Classification c,c1,c2,cn;
	REAL d1,d2;
	register int i,n;
	Index *ind,*ind2,*first,*last;
	Vertex *v;
	Point3D *p;
	REAL t;
	
	*frontP = *backP = NOFACE;
	
	v = VERTEXOfFace(poly);	
		
	/*get the classification for the first point*/
	ind = first = FIRSTOfFace(poly);
	last = LASTOfFace(poly);	
	
	p = GETPointFromIndex(v,first);	
	/*
	c = classifyPoint3D(plane,p,&d1);
	*/
	CLASSIFYPoint3D(plane,(*p),d1,c);
	
	while(ind != last){
		ind2 = NEXT(ind);
		p = GETPointFromIndex(v,ind2);
		/*
		c2 = addClassification(c,classifyPoint3D(plane,p,&d2));
		*/
		CLASSIFYPoint3D(plane,(*p),d2,cn);
		ADDClassification(c,cn,c2);
		if(c2 == FRONT_BACK || c2 == BACK_FRONT) {
			t = d1/(d1-d2);
			splitFace(poly,plane,c2,ind,ind2,t,frontP,backP);
			return(c2);
		}
		else{
			c = c2;
			ind = ind2;
			d1 = d2;
		}
	} 
			
	/*closure - note if the plane intersected this last edge
				then this would have already happened*/
	return(c);
}
			

Classification classificationOfFace(poly,plane)
/*
  returns the classification of the face with respect
  to the plane. If the classification is CUT then frontP
  and backP are the respective new polygons
*/
Face *poly;
PlaneEq *plane;
{
	Classification c,c1,c2,cn;
	REAL d;
	register int i,n;
	Index *ind,*ind2,*first,*last;
	Vertex *v;
	Point3D *p;
		
	v = VERTEXOfFace(poly);	
		
	/*get the classification for the first point*/
	ind = first = FIRSTOfFace(poly);
	last = LASTOfFace(poly);	
	
	p = GETPointFromIndex(v,first);	
	/*
	c = classifyPoint3D(plane,p,&d);
	*/
	CLASSIFYPoint3D(plane,(*p),d,c);
	
	while(ind != last){
		ind2 = NEXT(ind);
		p = GETPointFromIndex(v,ind2);
		/*
		c2 = addClassification(c,classifyPoint3D(plane,p,&d));
		*/
		CLASSIFYPoint3D(plane,(*p),d,cn);
		ADDClassification(c,cn,c2);
		if(c2 == FRONT_BACK || c2 == BACK_FRONT) 
			return(c2);
		else{
			c = c2;
			ind = ind2;
		}
	} 
			
	/*closure - note if the plane intersected this last edge
				then this would have already happened*/
	return(c);
}		


BOOLEAN commonVertexWithFace(face,poly)
/*returns true if the poly shares a vertex with the face*/
Face *face,*poly;
{
	Index *indF,*indP;
	
	if((face == NOFACE) || (poly == NOFACE)) return(FALSE);
	if(VERTEXOfFace(face) != VERTEXOfFace(poly)) return(FALSE);
			
	indF = face->first;
	while(indF != face->last)
	{
		indP = poly->first;
		while(indP != poly->last){
			if(INDEX(indF) == INDEX(indP)) return(TRUE);
			indP = indP->next;
		}
		if(INDEX(indF) == INDEX(indP)) return(TRUE);
		
		indF = indF->next;
	}
	indP = poly->first;
	while(indP != poly->last){
		if(INDEX(indF) == INDEX(indP)) return(TRUE);
		indP = indP->next;
	}
	if(INDEX(indF) == INDEX(indP)) return(TRUE);
	else
	return(FALSE);
}



void transformFace(face,m)
Face *face;
TMatrix *m;
{
	traversePtrFace(face,&transformVertex,m);
}
 
	
	