#include "view.h"

#define SHADOWSIZE 20

#define XS(i) (Shadow.polygon[i].x)
#define YS(i) (Shadow.polygon[i].y)
#define ZS(i) (Shadow.polygon[i].z)

#define AT(p,i)	(p->polygon[(i) % p->n])
#define PUT(p,x)	{p.polygon[p.n++] = x;}

typedef struct{
	int n;
	Point3D polygon[SHADOWSIZE];
} ShadowPolygon;

static ShadowPolygon Shadow;

static void splitPolygon(poly,plane,c,i,t)
/*
  split poly into two between indices i-1 and i.
  t is the t value for the intersection.
*/
ShadowPolygon *poly;
PlaneEq *plane;
Classification c;
int i;
REAL t;
{
	Point3D p1,p2,p,pnew;
	int j,k,k1,k2,i2;
	Classification c1,c2,cn;
	REAL x,y,z,d1,d2;
	ShadowPolygon theFront,theBack;
	
	/*initialise polygons*/
	theFront.n = theBack.n = 0;
	
	/*get the end points of the split edge*/
	p1 = AT(poly,i-1);	
	p2 = AT(poly,i);	
		
	/*and compute the new point*/
	pnew.x = X(p1) + (X(p2) - X(p1))*t;
	pnew.y = Y(p1) + (Y(p2) - Y(p1))*t;
	pnew.z = Z(p1) + (Z(p2) - Z(p1))*t;
		
	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*/
	PUT(theBack,pnew);
	j = i;
	while(c2 == c1){
		PUT(theBack,AT(poly,j++));
		d1 = d2;
		p2 = AT(poly,j);	
		/*
		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 j-1, j*/
	p1 = AT(poly,j-1);	
	p2 = AT(poly,j);				
	t = d1/(d1-d2);
	p.x = X(p1) + (X(p2) - X(p1))*t;
	p.y = Y(p1) + (Y(p2) - Y(p1))*t;
	p.z = Z(p1) + (Z(p2) - Z(p1))*t;
	
	PUT(theBack,p);
		
	PUT(theFront,p);
	i2 = j;
	while((i2%poly->n) != i){
		PUT(theFront,AT(poly,i2++)); 
	}
	PUT(theFront,pnew);
		
	if(c == FRONT_BACK)
		Shadow = theFront;
	else
		Shadow = theBack;
}


static Classification classifyPolygon(poly,plane)
/*
  returns the classification of the face with respect
  to the plane. If the classification is CUT then the front split-off polygon
  is stored in the global Shadow
*/
ShadowPolygon *poly;
PlaneEq *plane;
{
	Classification c,c1,c2,cn;
	REAL d1,d2;
	register int i,j;
	REAL t;
	
	/*
	c = classifyPoint3D(plane,&AT(poly,0),&d1);
	*/
	CLASSIFYPoint3D(plane,AT(poly,0),d1,c);
	
	for(i=1;i<poly->n;++i){
		/*
		c2 = addClassification(c,classifyPoint3D(plane,&AT(poly,i),&d2));
		*/
		CLASSIFYPoint3D(plane,AT(poly,i),d2,cn);
		ADDClassification(c,cn,c2);
		if(c2 == FRONT_BACK || c2 == BACK_FRONT) {
			t = d1/(d1-d2); /*split from i-1 to i*/
			splitPolygon(poly,plane,c2,i,t);
			return(c2);
		}
		else{
			c = c2;
			d1 = d2;
		}
	} 
			
	/*closure - note if the plane intersected this last edge
				then this would have already happened*/
	return(c);
}
			

Classification classificationOfPolygon(poly,plane)
/*
  returns the classification of the face with respect
  to the plane. 
*/
ShadowPolygon *poly;
PlaneEq *plane;
{
	Classification c,c1,c2,cn;
	REAL d1,d2;
	register int i,j;
	REAL t;
	
	/*
	c = classifyPoint3D(plane,&AT(poly,0),&d1);
	*/
	CLASSIFYPoint3D(plane,AT(poly,0),d1,c);
	
	for(i=1;i<poly->n;++i){
		/*
		c2 = addClassification(c,classifyPoint3D(plane,&AT(poly,i),&d2));
		*/
		CLASSIFYPoint3D(plane,AT(poly,i),d2,cn);
		ADDClassification(c,cn,c2);
		if(c2 == FRONT_BACK || c2 == BACK_FRONT) {
			return(c2);
		}
		else{
			c = c2;
			d1 = d2;
		}
	} 
			
	/*closure - note if the plane intersected this last edge
				then this would have already happened*/
	return(c);
}

static void sameShadowPolygon(poly,shadowFace)
/*
  In this case the shadow polygon is the same as the original.
  The point of all this is to avoid duplicating the same vertices
*/
Face *poly;
FacePtr *shadowFace;
{
	Vertex *v;
	Index *index;
	PlaneEq *pe;
	Shading *sh;
	
	v = VERTEXOfFace(poly);
	*shadowFace = newFace(v,INDEX(FIRSTOfFace(poly)));
	
	index = FIRSTOfFace(poly);
	while(index != LASTOfFace(poly)){
		index = NEXT(index);
		addToBackFace(*shadowFace,INDEX(index));
	}
	
	/*give same plane equation and shading as poly*/
	pe = copyPlaneEqOfFace(poly);
	sh = copyShadingOfFace(poly);
	(*shadowFace)->coeff = pe;
	(*shadowFace)->shading = sh;
	
	/*give the new shadowFace the same lightsAway set as poly*/
	(*shadowFace)->lightsAway = copyIndex(poly->lightsAway);	
}



static void setShadowPolygon(poly,shadowFace)
/*this constructs a totally new face from the Shadow*/
Face *poly;
FacePtr *shadowFace;
{
	Vertex *v;
	register int i,k;
	PlaneEq *pe;
	Shading *sh;
	
	v = VERTEXOfFace(poly);
	
	/*put the first point of the shadow into the vertex list*/
	k = NEXTFreeVertex(v);
	atPutCoordVertex(v,k,XS(0),YS(0),ZS(0));
	*shadowFace = newFace(v,k);
	
	for(i=1;i<Shadow.n;++i){
		k = NEXTFreeVertex(v);
		atPutCoordVertex(v,k,XS(i),YS(i),ZS(i));
		addToBackFace(*shadowFace,k);
	}
	
	/*give same plane equation and shading as poly*/
	pe = copyPlaneEqOfFace(poly);
	sh = copyShadingOfFace(poly);
	(*shadowFace)->coeff = pe;
	(*shadowFace)->shading = sh;
	
	/*give the new shadowFace the same lightsAway set as poly*/
	(*shadowFace)->lightsAway = copyIndex(poly->lightsAway);	
}


static void setShadow(v,i)
Vertex *v;
int i;
{
	Shadow.polygon[Shadow.n++] = *atVertex(v,i);
}


void shadowPolygonOfFace(face,poly,n,plane,shadowFace,sameShadow)
/*
  given a polygon and an array of n planes, this computes the shadow polygon
  constructed by those planes. The face is the original shadow casting face
*/
Face *poly,*face;
int n;
PlaneEq plane[];
FacePtr *shadowFace;
BOOLEAN *sameShadow;
{	
	register int i;
	Classification c;
	BOOLEAN inside,outside;
	ShadowPolygon polygon;
	
	*sameShadow = FALSE;
	
	/*copy the poly into the polygon array*/
	Shadow.n = 0;
	traverseFace(poly,setShadow);
	
	outside = FALSE;
	inside = TRUE;
	for(i=0;i<n;++i){
		if((c = classificationOfFace(poly,&plane[i]))==BACK){
			outside = TRUE;
			break;
		}
		inside = inside && ((c==FRONT)||(c==ON));
	}
	
	if(outside) *shadowFace = NOFACE;
	else
	if(inside) {
		sameShadowPolygon(poly,shadowFace);
		*sameShadow = TRUE;
	}
	else
	/*the shadow projection cuts the face*/
	{
		/*set the scratchShadow to something*/
		*shadowFace = (Face *)1;
		for(i=0;i<n;++i){
			polygon = Shadow;
			c = classifyPolygon(&polygon,&plane[i]);
			if(c == BACK) {
				*shadowFace = NOFACE;
				break;
			}
		}
		/*provided that the scratchShadow has not been set to NOFACE*/
		if(*shadowFace != NOFACE){ 
			/*check if behind the shadow casting face*/
			if(classificationOfPolygon(&Shadow,PLANEQOfFace(face))!= BACK)
					*shadowFace = NOFACE;
			else 	setShadowPolygon(poly,shadowFace);
		}
	}
}
	


static BOOLEAN shadowInscribedInFace(face,poly,light)
Face *face,*poly;
Light *light;
{
	REAL d1,d2,t;
	register int i;
	register Index *ind;
	PlaneEq *targetPlane,shadowPlane[10];
	Point3D *v,p;
	int n;
	Classification cn;
	
	/*get the shadow planes*/
	n = findPlaneEquations(poly,light,shadowPlane);
	
	/*
	classifyPoint3D((targetPlane=PLANEQOfFace(poly)),&light->position,&d1);
	*/
	targetPlane = PLANEQOfFace(poly);
	CLASSIFYPoint3D(targetPlane,light->position,d1,cn);
	
	ind = face->first;
	while(ind != face->last)
	{
		/*get the intersection of the vertex with the target plane*/
		/*
		classifyPoint3D(targetPlane,(v = atVertex(face->vertex,INDEX(ind))),&d2);
		*/
		v = atVertex(face->vertex,INDEX(ind));
		CLASSIFYPoint3D(targetPlane,(*v),d2,cn);
		t = d1/(d1-d2);
		p.x = X(light->position) + (X(*v) - X(light->position))*t;
		p.y = Y(light->position) + (Y(*v) - Y(light->position))*t;
		p.z = Z(light->position) + (Z(*v) - Z(light->position))*t; 
		
		/*now test the point against each of the shadow planes*/
		for(i=0;i<n;++i){
			/*
			if(classifyPoint3D(&shadowPlane[i],&p,&d2)==BACK) return(FALSE);
			*/
			CLASSIFYPoint3D((&shadowPlane[i]),p,d2,cn);
			if(cn==BACK) return(FALSE);
		}
		
		ind = NEXT(ind);
	}
	/*get the intersection of the vertex with the target plane*/
	/*
	classifyPoint3D(targetPlane,(v = atVertex(face->vertex,INDEX(ind))),&d2);
	*/
	v = atVertex(face->vertex,INDEX(ind));
	CLASSIFYPoint3D(targetPlane,(*v),d2,cn);
	t = d1/(d1-d2);
	p.x = X(light->position) + (X(*v) - X(light->position))*t;
	p.y = Y(light->position) + (Y(*v) - Y(light->position))*t;
	p.z = Z(light->position) + (Z(*v) - Z(light->position))*t; 
		
	/*now test the point against each of the shadow planes*/
	for(i=0;i<n;++i){
		/*
		if(classifyPoint3D(&shadowPlane[i],&p,&d2)==BACK) return(FALSE);
		*/
		CLASSIFYPoint3D((&shadowPlane[i]),p,d2,cn);
		if(cn==BACK) return(FALSE);
	}
		
	return(TRUE);
}


void setShadowsFromFace(face,poly,facelist,n,plane,light,lightNo,inscribed,sameShadow)
Face *face,*poly;
FaceList *facelist;
int n;
PlaneEq plane[];
Light *light;
int lightNo;
BOOLEAN *inscribed,*sameShadow;
{
	register int i;
	Colour colour;
	register Index *ind;
	Face *shadowFace,*shadowOfPoly;
	BOOLEAN insc,same;
	
	*inscribed = FALSE;
		
	shadowPolygonOfFace(face,poly,n,plane,&shadowFace,sameShadow);
	
	/*now this poly may have a shadow polygon computed*/
	if(shadowFace != NOFACE){		
		/*set the shading */
		shadowFace->shading->noOfLights--;
		REDOfFace(shadowFace) -= REDLightOfFace(poly,lightNo);
		GREENOfFace(shadowFace) -= GREENLightOfFace(poly,lightNo);
		BLUEOfFace(shadowFace) -= BLUELightOfFace(poly,lightNo);
		REDLightOfFace(shadowFace,lightNo) = 0.0;
		GREENLightOfFace(shadowFace,lightNo) = 0.0;
		BLUELightOfFace(shadowFace,lightNo) = 0.0;
		
		shadowFace->lightsAway = addToFrontIndex(shadowFace->lightsAway,lightNo);
	
		if(poly->noOfShadows > 0){
			ind = poly->shadow;
			while(ind != NOINDEX){
				shadowOfPoly = atFaceList(facelist,INDEX(ind));
				if(!memberOfIndex(shadowOfPoly->lightsAway,lightNo)){
					*inscribed = FALSE;
					setShadowsFromFace(face,shadowOfPoly,facelist,
											n,plane,light,lightNo,&insc,&same);
				}
				ind = NEXT(ind);
			}
		}

		appendToIndex(&poly->shadow,NEXTFreeFaceList(facelist),&poly->lastShadow);
		atPutFaceList(facelist,NEXTFreeFaceList(facelist),shadowFace);
		++poly->noOfShadows;
		if(*inscribed)*inscribed = shadowInscribedInFace(face,poly,light);
	}
}

